我是否必须为每个新数据类型定义一个函数



我正在使用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 种选择

  1. Vector转到列表,

    import Data.Vector as V
    dot a b = sum . V.toList $ a * b
    

    简单,但不必要的慢。

  2. 使用更一般的sum

    import Data.Foldable as F
    dot a b = F.sum $ a * b
    

    灵活,可能会导致奇怪的类型错误,因为我们依赖于另一个类型类。

  3. 使用不同的特定(花哨的词是单态的)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实现多态性的主要机制。特别是,你说类型tFoldable的实例,如果你能把它想象成包含可以"粉碎"在一起的特定顺序的元素。这几乎描述了一个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,也可以让你派生FoldableGeneralizedNewtypeDeriving功能更强大,但具体来说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 IntVector 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

相关内容

  • 没有找到相关文章