如果我在Haskell中有两个相同大小的列表
list1 = [1.0,2.0,3.0]
list2 = [3.0,5.0,7.0]
我如何执行元素添加以创建相同大小的第三个列表?
[4.0,7.0,10.0]
具体来说,我想制作一个这样的函数:
listAdd :: [Float] -> [Float] -> [Float]
listAdd a b
| length a /= length b = error "length mismatch"
otherwise = ????
我无法弄清楚应该用什么代替'????'。我认为它必须涉及'map'和某些版本的'+',但是部分评估的东西让我感到困惑,并且正确的语法已经证明是难以捉摸的。
编辑1:
我以为我理解了与cons运算符匹配的模式,所以我接下来尝试了这个:
listAdd :: [Float] -> [Float] -> [Float]
listadd (x:xs) (y:ys) = (x+y) : listAdd xs ys
listAdd [] [] = []
listAdd _ _ = error "length mismatch"
但仍有问题,如
listAdd [1.0,2.0] [2.0,3.0]
越过有用的模式并返回错误。
编辑2:
删除拼写错误,
listAdd :: [Float] -> [Float] -> [Float]
listAdd (x:xs) (y:ys) = (x+y) : listAdd xs ys
listAdd [] [] = []
listAdd _ _ = error "length mismatch"
按广告宣传。由于在我的研究的早期阶段管理任意维度的张量类型超出了我的范围,我决定只将它扩展到矩阵加法:
mAdd :: [[Float]] -> [[Float]] -> [[Float]]
mAdd (x:xs) (y:ys) = listAdd x y : mAdd xs ys
mAdd [] [] = []
mAdd _ _ = error "length mismatch"
我意识到将功能结合在一起可能并不理想,因为它降低了模块化/可移植性,但它完成了我需要它做的事情。
编辑3:
我希望宣布某种程度的学习已经发生还为时过早。我现在有这个:
listCombine :: (Float -> Float -> Float) -> [Float] -> [Float] -> [Float]
listCombine f (x:xs) (y:ys) = (f x y) : listCombine f xs ys
listCombine f [] [] = []
listCombine _ _ _ = error "length mismatch"
可能与zipWith相同,只是它给出了不匹配长度的错误。它处理了我投掷的角落案件,并提供了预期的结果。
答案 0 :(得分:4)
单独计算参数列表的长度是一个坏主意。我们通常希望尽可能少地使用输入,同时尽可能多地生成输出。这被称为“不强迫输入”,即尽可能地懒惰。
在你的情况下,当我们分析这两个参数时,如果一个列表为空而另一个列表为空,我们将知道长度不匹配:
listAdd :: [Float] -> [Float] -> [Float]
listAdd (x:xs) (y:ys) = (x+y) : listAdd ... ...
listAdd [] [] = []
listAdd _ _ = error "length mismatch"
这样它甚至可以用于无限列表。
与此类似的内置函数是zipWith
,但它忽略了列表长度不匹配:
Prelude> zipWith(+) [1,2] [3]
[4]
它等同于上面的定义,最后两行用catch-all子句替换
listAdd _ _ = []
答案 1 :(得分:3)
这不是原始问题,而是其中一条评论:增加了张量。我对张量的理解是作为n维矩阵。您已经知道元素添加只是zipWith
的应用程序。但是我们可以使用applicative和functor类类来编写函数addLists
:
import Control.Applicative
addLists :: [Float] -> [Float] -> [Float]
addLists x y = (*) <$> x <*> y
或等效地:
addLists :: [Float] -> [Float] -> [Float]
addLists = liftA2 (*)
请注意,<$> == fmap
和<*>
位于Applicative
类中。
首先我们为rank-0张量定义一个类型:
newtype Identity a = Identity {getIdentity :: a}
instance Functor Identity where
fmap f (Identity a) = Identity (f a)
instance Applicative Identity where
pure a = Identity a
(<*>) (Identity f) (Identity a) = Identity (f a)
然后在rank-n张量之上添加一个新层的类型,创建一个n + 1张量的等级:
newtype Compose f g a = Compose {getCompose :: f (g a)}
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose a) = Compose (fmap (fmap f) a)
instance (Applicative f, Applicative g) => Applicative (Compose f g) where
pure a = Compose (pure (pure a))
(<*>) (Compose f) (Compose a) = Compose (liftA2 (<*>) f a)
但等等,还有更多!已经为您定义了这些类型及其实例,即Data.Functor.Identity
和Data.Functor.Compose
。您现在可以编写通用张量元素添加:
addTensor :: Applicative f => f Float -> f Float -> f Float
addTensor = liftA2 (*)
函数liftA2
只是将zipWith
概括为Applicative
。对于排名-1,你有:
addTensor1 :: Compose [] Identity Float -> Compose [] Identity Float -> Compose [] Identity Float
addTensor1 = addTensor
这种类型有点吵。您可以轻松定义类型同义词:
type Tensor0 = Identity
type Tensor1 = Compose [] Tensor0
type Tensor2 = Compose [] Tensor1
type Tensor3 = Compose [] Tensor2
type Tensor4 = Compose [] Tensor3
然后:
addTensor3 :: Tensor3 Float -> Tensor3 Float -> Tensor3 Float
addTensor3 = addTensor
addTensor4 :: Tensor4 Float -> Tensor4 Float -> Tensor4 Float
addTensor4 = addTensor
由于addTensor
对所有Applicative
进行抽象,您可能需要定义构成有效张量的Applicative
的子类:
class Applicative t => Tensor t
instance Tensor Identity
instance Tensor t => Tensor (Compose [] t)
addTensor :: Tensor f => f Float -> f Float -> f Float
addTensor = liftA2 (*)
我怀疑除了加法之外你可能还有其他的张量操作。这种通用形式使得定义大范围的操作变得非常容易。但假设你只想要addTensor
,你可以做类似的事情:
class Tensor a where
addTensor :: a -> a -> a
instance Tensor Float where
addTensor = (+)
instance Tensor t => Tensor [t] where
addTensor = zipWith addTensor
更简单的类型结果:addTensor :: [[[[Float]]]] -> [[[[Float]]]] -> [[[[Float]]]]
为rank-4。