我正在使用Haskell,并将Vector
声明为
data Vector = Vector [Double]
现在,我想声明两个向量的dot
积为
dot :: Vector -> Vector -> Double
dot a b = sum $ a * b -- I already wrote Vector as an instance of Num for *.
但是,问题是,我收到错误
Couldn't match expected type [a0] with actual type Vector
我认为这意味着sum
不知道如何在Vector
上进行操作。解决此问题的最佳方法是什么?
所以我注意到你没有使用标准向量。我建议切换到它们,但如果你真的不想,
toList :: Vector -> [Double]
toList (Vector a) = a
和使用
dot a b = sum . toList $ a * b
如果您切换到标准向量,您有 3 种选择
将
Vector
转到列表,import Data.Vector as V dot a b = sum . V.toList $ a * b
简单,但不必要的慢。
使用更一般的
sum
import Data.Foldable as F dot a b = F.sum $ a * b
灵活,可能会导致奇怪的类型错误,因为我们依赖于另一个类型类。
使用不同的特定(花哨的词是单态的)
sum
import Data.Vector as V dot a b = V.sum $ a * b
最简单的,但当然,如果你停止使用向量,这将中断。
我推荐选项 3,还不需要过于笼统。
一般来说,是的。通常这是可取的,因为它允许您删除您不希望人们访问的功能。例如,为用户提供[Double]
可以让他们计算长度并将其作为链表进行检查,而newtype Vector = Vector [Double]
可以让您公开vectorLength
,当且仅当您觉得这是一个好主意时。
但这不是眼前的问题。您立即希望能够在Vector
类型上进行操作,而无需重新定义您能想到的每个有用函数。幸运的是,有很多方法可以解决这个问题。
您可以将Vector
定义为type
同义词,而不是新的具体类型。这让Haskell可以透明地将Vector
解释为[Double]
,并自动使用列表函数的完整补充
type Vector = [Double]
vectorSum :: Vector -> Double
vectorSum = sum
你可以,虽然你试图避免它,也可以直接编写你自己的vectorSum
。
vectorSum :: Vector -> Double
vectorSum (Vector list) = sum list
一般来说,它在实际代码中看起来有点不同,因为人们倾向于滥用记录语法来为Vector
制作一个简单的"逃生舱口"
data Vector = Vector { unVector :: [Double] }
vectorSum :: Vector -> Double
vectorSum = sum . unVector
manySums :: [Double]
manySums = map (v -> sum (unVector v)) makeLotsOfVectors
您可以将Vector
定义为Foldable
的实例。 Foldable
是一个类型类,是Haskell实现多态性的主要机制。特别是,你说类型t
是Foldable
的实例,如果你能把它想象成包含可以"粉碎"在一起的特定顺序的元素。这几乎描述了一个Vector
和一个sum
,所以
import Prelude hiding (foldl)
import Data.Foldable (Foldable, foldl, foldMap)
data Vector a = Vector [a] -- note that the type is parametric, this is
-- required for Foldable
foldableSum :: (Foldable t) => t Double -> Double
foldableSum = foldl (+) 0
instance Foldable Vector where
foldMap f (Vector list) = foldMap f list -- it just inherits from the
-- Foldable [] instance
vectorSum :: Vector Double -> Double
vectorSum = foldableSum
您还可以使用称为GeneralizedNewtypeDeriving
的GHC Haskell非常方便的机制来自动发生这些繁琐的实例。为此,我们必须注意Vector
与[]
---它实际上只是一个新名称非常相似。这意味着我们可以使用newtype
而不是data
。
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype Vector a = Vector [a] deriving ( Foldable )
vectorSum :: Vector Double -> Double
vectorSum = foldl (+) 0
有趣的是,GHC Haskell还有一个扩展,即使你没有newtype,也可以让你派生Foldable
。 GeneralizedNewtypeDeriving
功能更强大,但具体来说Foldable
我们不需要使用它。
{-# LANGUAGE DeriveFoldable #-}
data Vector a = Vector [a]
vectorSum :: Vector Double -> Double
vectorSum = foldl (+) 0
还有其他人提到的非常强大的vector
库,它可以完成所有这些以及更多。
由于您没有使用Data.Vector
向量,因此您实际上无法sum
直接处理您的数据类型,因为它的类型是
sum :: Num a => [a] -> a
你给它Vector [Double]
而不是Num a => [a]
. 您必须先提取向量中的列表:
toList :: Vector -> [Double]
toList (Vector vals) = vals
dot :: Vector -> Vector -> Double
dot a b = sum . toList $ a * b
话虽如此,您可能应该只使用 Data.Vector
提供的向量,或者至少您应该将Vector
类型定义为
{-# LANGUAGE DeriveFunctor #-}
import Control.Applicative
data Vector a = Vector [a] deriving (Eq, Ord, Show, Functor)
instance Applicative Vector where
pure a = Vector [a]
(Vector fs) <*> (Vector xs) = Vector $ zipWith ($) fs xs
instance Num a => Num (Vector a) where
a + b = (+) <$> a <*> b
a * b = (*) <$> a <*> b
-- etc.
然后你可以有Vector Int
,Vector Double
,甚至Vector (Int -> Double)
,因为它现在是一个Functor
和一个Applicative
,你可以用它做更多的事情,正如这个例子所暗示的。
您正在使用 Prelude 中的总和,类型为:
sum :: Num a => [a] -> a
向量的总和在 Data.Vector 中定义(通常导入限定)
编辑:我错过了您正在使用自己的数据类型的事实,而不是来自 Data.Vector 的数据类型
创建一个点函数,你可以做
data Vector = Vector [Double]
dot :: Vector -> Vector -> Double
dot (Vector a) (Vector b) = sum $ zipWith (*) a b
这样,"a"和"b"现在是向量内部的[双精度],而不是向量本身。
循序渐进:
dot (Vector [1,2]) (Vector [3,4]) = sum $ zipWith (*) [1,2] [3,4]
= sum $ zipWith (*) [1,2] [3,4]
= sum $ [1*3, 2*4]
= 1*3 + 2*4
= 3 + 8
= 11