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

时间:2013-10-30 19:18:43

标签: haskell types linear

我正在使用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。解决这个问题的最佳方法是什么?

5 个答案:

答案 0 :(得分:6)

所以我注意到你没有使用标准矢量。我建议改用它们,但如果你真的不想,

 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
    

    最简单,但当然,如果你停止使用矢量,这将会破坏。

  4. 我建议选项3,不需要过于笼统。

答案 1 :(得分:3)

一般来说,是的。通常这是可取的,因为它允许您删除不希望人们访问的功能。例如,给予用户[Double]可以让他们计算长度并将其作为链接列表进行检查,而newtype Vector = Vector [Double]会让vectorLength公开Vector当且仅当您认为这是一个好主意时

但那不是手头的问题。您希望能够在Vector类型上运行,而无需重新定义您能想到的所有有用功能。幸运的是,有很多方法可以解决这个问题。

您可以将type定义为Vector同义词,而不是新的具体类型。这让Haskell透明地将[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的实例。 t是一个类型类,是Haskell实现多态的主要机制。特别是,如果您认为类型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,所以

Vector

您还可以使用一种非常方便的GHC Haskell机制[]来使这些繁琐的实例自动发生。要做到这一点,我们必须注意newtype 非常类似于data ---它实际上只是一个新名称。这意味着我们可以使用{-# LANGUAGE GeneralizedNewtypeDeriving #-} newtype Vector a = Vector [a] deriving ( Foldable ) vectorSum :: Vector Double -> Double vectorSum = foldl (+) 0 代替Foldable

GeneralizedNewtypeDeriving

有趣的是,GHC Haskell还有一个扩展,即使您没有新类型,也可以派生Foldable{-# LANGUAGE DeriveFoldable #-} data Vector a = Vector [a] vectorSum :: Vector Double -> Double vectorSum = foldl (+) 0 更强大,但对于vector,我们不需要使用它。

{{1}}

还有其他人提到的非常强大的{{1}}库可以完成所有这些以及更多功能。

答案 2 :(得分:2)

您使用的是Prelude中的sum,类型为:

sum :: Num a => [a] -> a

向量的总和在Data.Vector中定义(通常是导入的限定)

编辑:我错过了你使用自己的数据类型的事实,而不是Data.Vector中的数据类型

答案 3 :(得分:2)

由于您没有使用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),因为现在它是FunctorApplicative,你可以做更多的事情正如这个例子所暗示的那样。

答案 4 :(得分:1)

创建一个你可以做的点功能

data Vector = Vector [Double]

dot :: Vector -> Vector -> Double
dot (Vector a) (Vector b) = sum $ zipWith (*) a b

这样'a'和'b'现在是Vector内部的[Double],而不是Vector的自我。

步骤一步:

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