我已经走过了“现实世界的哈克尔”,并且认为我已经开始编写我的第一个代码了。没走得太远......
基本上,我以为我会尝试为双打和双列表实现数组语法,这样我就可以对列表进行数值计算。我认为'class'就像CLOS中的通用函数一样吗?我使用函数arplus创建了一个“类”来添加数组和标量,并使其适用于标量。现在我不知道如何为列表做同样的事情。 (进行计算很简单,我只是不知道用什么代替'Double'作为列表版本。)
class Narray a where
arplus :: a -> a -> a
instance Narray Double where
arplus a b = a + b
然后,我该如何做混合版本?数组标量和标量数组?
我需要这样的东西:
class Narray a where
arplus :: a -> b -> a
代替?
作为我正在尝试编写的代码示例,请参阅下面的sbcl代码: (注意 - 我在lisp中使用数组做了这个,但是我在输入时在haskell的列表中这样做。虽然在问题方面并不重要。)
;; Generic add for arrays and scalar
(defgeneric .+ (a b))
;; Scalar-scalar
(defmethod .+ ((a double-float) (b double-float)) (+ a b))
;; Array-array
(defmethod .+ ((a SB-KERNEL::SIMPLE-ARRAY-DOUBLE-FLOAT)
(b SB-KERNEL::SIMPLE-ARRAY-DOUBLE-FLOAT))
(dotimes (i (array-total-size a))
(setf (row-major-aref a i)
(+ (row-major-aref a i)
(row-major-aref b i)))) a)
;; Array-scaler
(defmethod .+ ((a SB-KERNEL::SIMPLE-ARRAY-DOUBLE-FLOAT) (b double-float))
(dotimes (i (array-total-size a))
(setf (row-major-aref a i)
(+ (row-major-aref a i)
b))) a)
;; Scalar-array
(defmethod .+ ((a double-float) (b SB-KERNEL::SIMPLE-ARRAY-DOUBLE-FLOAT))
(dotimes (i (array-total-size b))
(setf (row-major-aref b i)
(+ a
(row-major-aref b i)))) b)
;; Just to demo the code
(defun indgen (n)
(let ((r (make-array n :element-type 'double-float)))
(dotimes (i n)
(setf (row-major-aref r i) (coerce i 'double-float))) r))
* (load "arrays.lisp")
T
* (.+ (indgen 6) 10d0)
#(10.0d0 11.0d0 12.0d0 13.0d0 14.0d0 15.0d0)
* (.+ (indgen 6) (indgen 6))
#(0.0d0 2.0d0 4.0d0 6.0d0 8.0d0 10.0d0)
*
答案 0 :(得分:3)
基本上,我以为我会尝试为双打和双列表实现数组语法,这样我就可以对列表进行数值计算。我认为'class'就像CLOS中的泛型函数一样正确吗?
Haskell的类型类是实现有时称为“ad-hoc多态”的一种方法,这意味着函数可以在多种类型上运行,但每种类型都可以执行不同的操作。它们与大多数其他语言中的任何东西都不完全可比,但从我对它的了解很少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的评论所说,这个类型类混合了概念上不同的操作,这样做没什么好处。
我不确定对更好的整体设计的讨论是否会对您有用,但足以说明在这种情况下我根本不会打扰类型类。
答案 1 :(得分:3)
无法回答整个问题,但对于列表版本,我让这个工作:
main = [1, 2, 3] `arplus` [4, 5, 6]
class Narry a where
arplus :: a → a → a
instance (Num a) ⇒ Narry [a] where
arplus = zipWith (+)
我也试过转(Num a)=> [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]
当然,如果你想对列表进行双重算术,那么你将不得不为更多东西编写定义。我不确定我的定义在多大程度上可以做到,但我怀疑它不会比我所展示的更多。
此外,这不是使Nums列表成为Num实例的唯一方法;对于(*)而言,可能有比zipWith更好的选择。
[编辑]使列表成为Fractional的实例非常简单,并增加了一些更方便的功能。
instance (Fractional a) => Fractional [a] where
recip = map recip
fromRational = repeat . fromRational
这使得list-with-fractional交互成为可能,并且自动神奇地划分工作!
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]
强大的力量带来了巨大的责任;只有在需要时才使用它们。如果您不需要这种行为,那么当您尝试在数字和列表上调用+
时,让编译器对您大喊大叫是件好事。