Haskell 试图理解类和实例

Haskell 试图理解类和实例,haskell,Haskell,我已经看完《真实世界哈斯克尔》一半了,我想我应该试着写我的第一段代码。没走多远 基本上,我想我应该尝试实现一个双精度和双精度列表的数组语法,这样我就可以对列表进行数值计算了。我认为“类”就像CLOS中的泛型函数,对吗?我制作了一个带有函数arplus的“类”来添加数组和标量,并使其适用于标量。现在我不知道如何对列表执行同样的操作。(做计算很容易,我只是不知道用什么来代替列表版本的“Double”) 那么,我如何做混合版本?数组标量和标量数组 我是否需要一些类似于: class Narray a

我已经看完《真实世界哈斯克尔》一半了,我想我应该试着写我的第一段代码。没走多远

基本上,我想我应该尝试实现一个双精度和双精度列表的数组语法,这样我就可以对列表进行数值计算了。我认为“类”就像CLOS中的泛型函数,对吗?我制作了一个带有函数arplus的“类”来添加数组和标量,并使其适用于标量。现在我不知道如何对列表执行同样的操作。(做计算很容易,我只是不知道用什么来代替列表版本的“Double”)

那么,我如何做混合版本?数组标量和标量数组

我是否需要一些类似于:

class Narray a where
   arplus :: a -> b -> a
相反

作为我试图编写的代码示例,请参见下面的sbcl代码: (注意——我用lisp中的数组来做这件事,但我是在haskell中的列表中做的,因为它们是被键入的。不过,就问题而言,这并不重要。)

基本上,我想我应该尝试实现一个双精度和双精度列表的数组语法,这样我就可以对列表进行数值计算了。我认为“类”就像CLOS中的泛型函数,对吗

Haskell的类型类是一种实现有时被称为“特殊多态性”的方法,这意味着一个函数可以对多个类型进行操作,但对每个类型执行不同的操作。它们与大多数其他语言中的任何东西都不完全相似,但据我所知,CLOS的“泛型函数”听起来非常相似

那么,我如何做混合版本?数组标量和标量数组

我假设数组和标量是不同的类型——这显然是一个问题,因为类型类只有一个类型参数。如果您使用的是GHC,则有一个语言扩展名
MultiParamTypeClasses
,用于。。。基本上就是它说的

不幸的是,由于各种原因,这在实践中可能是一种痛苦,但在本例中,问题是您希望混合版本适用于任一参数顺序,但结果必须是两者的数组。最直接的方法是将结果设为第三个类型参数——但是Haskell不知道应用于数组的
arplus
,而标量是数组,因为类型参数彼此独立。为了解决这个问题,还存在其他扩展,但是对于某些任务,事情可能会很快变得复杂

现在,我对lisps有点生疏,无法确定我是否正确阅读了您的示例,但这里有一个我认为您想要的:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}

class Narray a b c | a b -> c where
    arplus :: a -> b -> c

instance Narray Double Double Double where 
    arplus = (+)

instance Narray Double [Double] [Double] where 
    arplus x = map (x +)

instance Narray [Double] Double [Double] where 
    arplus x y = map (+ y) x

instance Narray [Double] [Double] [Double] where 
    arplus = zipWith (+)
您可以查阅我在GHC文档中使用的扩展以了解更多详细信息

也就是说,请以上面的例子来说明如何进行简单的多分派样式重载,这在实践中并不一定是一个好主意——正如@luqui的评论所说,这个类型类将概念上不同的操作合并在一起,这样做几乎没有什么好处


我不确定关于更好的总体设计的讨论是否对您有用,但我只想说,在这种情况下,我根本不会为类型类而烦恼。

无法回答整个问题,但对于列表版本,我做到了这一点:

main = [1, 2, 3] `arplus` [4, 5, 6]

class Narry a where
    arplus :: a → a → a

instance (Num a) ⇒ Narry [a] where
    arplus = zipWith (+)
我还尝试将(numa)=>[a]转换为Num的一个实例,得到了一些不错的结果

instance (Num a) ⇒ Num [a] where
    (+) = zipWith (+)
    (*) = zipWith (*)
    (-) = zipWith (-)
    negate = map negate
    abs    = map abs
    signum = map signum
    fromInteger = repeat∘fromInteger
你可以像这样试一下

main = do print $ [1, 2, 3] * 3   -- [3, 6, 9]
          print $ 3 * [1, 2, 3]   -- [3, 6, 9]
          print $ 3 - [2, 4, 6]   -- [-1, 1, 3]
          print $ [2, 4, 6] + 7   -- [9, 11, 13]
          print $ abs [-2, 4, -3] -- [2, 4, 3]
          print $ [1, 2, 3] + [4.3, 5.5, 6.7] -- [5.3, 7.5, 9.7]
          print $ [1, 2, 3] * [3, 4, 5] -- [3, 8, 15]
当然,如果你想对列表进行双重运算,那么你必须为更多的东西编写定义。我不确定我在这里的定义能起到什么作用,尽管我怀疑它并不比我所演示的多多少

而且,这不是使Num列表成为Num实例的唯一方法;(*)可能有比zipWith更好的选择

[编辑]使列表成为分数的实例非常简单,并添加了一些更方便的功能

instance (Fractional a) => Fractional [a] where
    recip = map recip
    fromRational = repeat . fromRational
这使得具有分数交互的列表成为可能,并且divide自动神奇地工作

ghci> [3, 6, 9] / 3
[1.0,2.0,3.0]
ghci> 9 / [1, 2, 3]
[9.0,4.5,3.0]
ghci> 1.2 + [0, 1, 2]
[1.2,2.2,3.2]

权力大,责任大;只有在你需要的时候才使用。如果你不需要这种行为,那么当你试图调用一个数字和一个列表上的
+
时,编译器对你大喊大叫是件好事。

作为一个旁注,我发现现实世界中的Haskell在你用来补充材料时工作得非常好。你试图做的不是很好地运用Haskell的“说出你的意思”哲学。数组添加(压缩)与标量数组添加(映射)是一个不同的概念。因此,您将需要两个单独的运算符。当前约定在向量参数一侧使用
^
:例如,数组数组使用
^+^
,标量数组使用
+^
。AFAIK Haskell不支持类型上的多个分派,您可以编写类似于
类Narray a b c,其中arplus::a->b->c
,但是您必须显式标记所有类型。@adamax:不需要显式标记所有类型;只是要知道所有的类型。如果您编写类似于
arplus-True()++“abc”
,它将推断
Bool->()->String
很好。问题是指定对于每个特定的
a
b
,只有一个有效的
c
。谢谢您的回答。我想也许你最后的评论说明了一切。当你学习一门新的语言(很有趣的一门)时,最困难的部分往往是学习一种新的思维方式。您必须添加所有这些扩展的事实向我表明,我对这一切的想法都是错误的,我正在与haskell斗争,而不是编写haskell。在这里,为标量和列表的操作符制作一些东西的正确思维过程是什么?
instance (Fractional a) => Fractional [a] where
    recip = map recip
    fromRational = repeat . fromRational
ghci> [3, 6, 9] / 3
[1.0,2.0,3.0]
ghci> 9 / [1, 2, 3]
[9.0,4.5,3.0]
ghci> 1.2 + [0, 1, 2]
[1.2,2.2,3.2]