试图理解类和实例

时间:2011-01-21 23:42:44

标签: haskell

我已经走过了“现实世界的哈克尔”,并且认为我已经开始编写我的第一个代码了。没走得太远......

基本上,我以为我会尝试为双打和双列表实现数组语法,这样我就可以对列表进行数值计算。我认为'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)
* 

2 个答案:

答案 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]

强大的力量带来了巨大的责任;只有在需要时才使用它们。如果您不需要这种行为,那么当您尝试在数字和列表上调用+时,让编译器对您大喊大叫是件好事。