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