2012-05-16 60 views
15

我想要一个函数+++,它添加了两个数学向量。Haskell:列表和元组之间

我可以实现向量作为[x, y, z]及用途:

(+++) :: (Num a) => [a] -> [a] -> [a] 
(+++) = zipWith (+) 

并由此适应任何ň维向量(所以这会为[x, y]工作太)。

或者我可以实现向量作为(x, y, z)和使用:

type Triple a = (a, a, a) 

merge :: (a -> b -> c) -> Triple a -> Triple b -> Triple c 
merge f (a, b, c) (x, y, z) = (f a x, f b y, f c z) 

(+++) :: (Num a) => Triple a -> Triple a -> Triple a 
(+++) = merge (+) 

当然,这是稍微更复杂,但是它,当我实现所有的其它载体功能,即不相关(50行而不是40)。

列表方法的问题是我可以添加一个2D矢量与3D矢量。在这种情况下,zipWith将简单地切掉3D矢量的z组件。虽然这可能是有道理的(更可能它应该将2D矢量扩展到[x, y, 0]),但对于其他功能,我认为可能会有问题发生,无论是默默发生。元组方法的问题是它将矢量限制为3个组件。因为数学矢量具有固定数量的分量,所以将一个分量用于向量(前置)是没有意义的。直观上,我认为将向量表示为(x, y, z)会更有意义。另一方面,虽然我不太需要3D矢量以外的其他任何东西,但将其限制在这个范围似乎不太合适。

我想我想要的是采用两个相等长度或更好的函数对任意大小的元组进行操作的函数。

任何建议,在实用性,可扩展性,优雅等方面?

+0

http:// stackoverflow。com/questions/7220953/does-haskell-have-variadic-functions-tuples –

+0

我知道这个问题有点旧,但你可能想看看[vector-space](http://hackage.haskell .org/package/vector-space)包。 –

回答

-1

Landei's和leftaroundabout的答案很好(谢谢你们俩),我想我应该意识到这不会像我希望的那么简单。试图做我建议的选项中的任何一个都可以生成复杂的代码,除非看起来用户代码看起来不漂亮,否则这本身并不是一个问题。

我想我已经决定去使用元组,并坚持使用3维矢量,只是因为它看起来比使用列表语义更正确。尽管如此,我最终还是重新实施了mapzipWithsum和其他三元组。我想坚持简单 - 我觉得好像我有一个令人信服的论点来将向量想象为列表,那么解决方案会更好地工作(只要我确保不混合维度)......当我实际使用向量时,尽管函数会将3d向量作为参数,而不是可变维度之一,并且Num a => [a]无法强制执行此操作。

+0

使用'Data。来自'矢量'包或具有3D向量的'ACVector'包。这些库已经定义了帮助函数,为您节省时间和精力。 – vivian

+3

图书馆的代码可能很复杂,但你可以使用'type'定义和便利方法来隐藏它。 – Landei

14

最简单的方法就是把+++运营商在一个类型的类,并且使各个字段大小实例:

{-# LANGUAGE FlexibleInstances #-} -- needed to make tuples type class instances 

class Additive v where 
    (+++) :: v -> v -> v 

instance (Num a) => Additive (a,a) where 
    (x,y) +++ (ξ,υ) = (x+ξ, y+υ) 
instance (Num a) => Additive (a,a,a) where 
    (x,y,z) +++ (ξ,υ,ζ) = (x+ξ, y+υ, z+ζ) 
... 

这样,可变长度元组可以添加,但它会在编译保证双方总是有相同的长度。


归纳这在实际类型的类使用功能,喜欢你 merge也是可能的:在这种情况下,你需要指定类实例的类型构造(如列表单子)。

class Mergable q where 
    merge :: (a->b->c) -> q a -> q b -> q c 

instance Mergable Triple where 
    merge f (x,y,z) (ξ,υ,ζ) = (f x ξ, f y υ, f z ζ) 

,然后简单地

(+++) :: (Mergable q, Num a) => q a -> q b -> q c 
+++ = merge (+) 

不幸的是,这完全不是那么回事,因为类型同义词不可以部分进行评估。你需要让Triple一个NEWTYPE,而不是像

newtype Triple a = Triple(a,a,a) 

然后

instance Mergable Triple where 
    merge f (Triple(x,y,z)) (Triple((ξ,υ,ζ)) = Triple(f x ξ, f y υ, f z ζ) 

这当然是不太好看。

+3

@VladtheImpala:也许你更喜欢[日语](http://codegolf.stackexchange.com/a/4824/2183)? - 真的,调用局部变量希腊名字有什么问题?它强制任何人在他们自己的代码中输入它们,如果你知道希腊字母表,就可以将它与例如z和zeta,如果你不这样做,它和任意拉丁字母相比几乎没什么区别。 – leftaroundabout

21

您可以使用类型级别编程。首先,我们需要使每个自然数都是独立的类型。继自然数的皮亚诺的定义,Z0,并且S xx + 1

data Z = Z 
data S a = S a 

class Nat a 
instance Nat Z 
instance (Nat a) => Nat (S a) 

现在我们可以用一个类型Vec简单地包裹清单,但通过使用Nat跟踪它的大小。为此,我们使用smart constructorsnil<:>(所以你不应该从你的模块中导出数据构造Vec

data Vec a = Vec a [Int] 

nil = Vec Z [] 

infixr 5 <:> 
x <:> (Vec n xs) = Vec (S n) (x:xs) 

现在,我们可以定义一个add功能,这就要求两个向量具有相同的Nat

add :: Nat a => Vec a -> Vec a -> Vec a 
add (Vec n xs) (Vec _ ys) = Vec n (zipWith (+) xs ys) 

现在你有一个长信息的载体类型:

toList (Vec _ xs) = xs 
main = print $ toList $ add (3 <:> 4 <:> 2 <:> nil) (10 <:> 12 <:> 0 <:> nil) 

当然,这里具有不同长度的向量会导致编译错误。

这是易于理解的版本,有更短,更高效和/或更方便的解决方案。

+0

我想知道“更高效和/或更方便”的解决方案吗,有没有人关心给一些指点? – sinan

+0

我认为你可以用'HList'获得类似的效果:http://hackage.haskell.org/packages/archive/HList/0.2.3/doc/html/Data-HList-HListPrelude.html – Landei

1

由于OP想要一个更轻量级的方法,我会使用关联的类型。

class VecMath a b where 
    type Res a b :: * 
    (+++) :: a -> b -> Res a b 

instance Num a => VecMath (a,a,a) (a,a,a) where 
    type Res (a,a,a) (a,a,a) = (a,a,a) 
    (x1,y1,z1) +++ (x2,y2,z2) = (x1+x2, y1+y2, z1+z2) 

instance Num a => VecMath (a,a) (a,a,a) where 
    type Res (a,a) (a,a,a) = (a,a,a) 
    (x1,y1) +++ (x2,y2,z) = (x1+x2, y1+y2, z) 

instance Num a => VecMath (a,a,a) (a,a) where 
    type Res (a,a) (a,a,a) = (a,a,a) 
    -- (+++) analog 
instance Num a => VecMath (a,a) (a,a) where 
    type Res (a,a) (a,a) = (a,a) 
    -- ... 

Res是一种功能,本质在这里造成的它的参数的“大”类型。好处是你仍然可以使用普通的旧元组,就好像VecMath不存在。如果您考虑在Res的域中添加新类型,那么黑暗的一面是您必须编写的实例的指数爆炸。 欲了解更多信息,请参阅this