【问题标题】:Haskell Custom Math Types and ClassesHaskell 自定义数学类型和类
【发布时间】:2012-03-12 09:11:13
【问题描述】:

有没有办法让类实例返回一个不是实例类型的值?一个例子是想要为两个向量的标量积返回一个 Double 类型的值:

--  data structure to contain a 3D point in space
data Point3D = Point3D !Double !Double !Double
    deriving (Eq, Ord)

instance Num Point3D where
    -- Multiplication, scalar == Dot product
    Point3D x1 y1 z1 * Point3D x2 y2 z2 = x1*x2 + y1*y2 + z1*z2 :: Double

此外,有没有办法定义运算符在不同类型的函数之间的工作方式?比如我想定义Point3D x y z + Double a = Point3D (x + a) (y + a) (z + a)

【问题讨论】:

标签: class math haskell types operator-overloading


【解决方案1】:

Num 类型类中的数值运算都是用:: Num n => n -> n -> n 类型定义的,因此操作数和返回值必须具有相同的类型。无法更改现有的类型类,因此您的选择是定义新的运算符或隐藏现有的 Num 类并用您自己的实现完全替换它。

为了实现可以具有不同操作数类型的运算符,您将需要一些语言扩展。

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

与包含+-* 的类似Num 的类不同,为不同的操作数定义不同的类型类更加灵活,因为虽然Point3D * Double 有意义,但Point3D + Double 通常这样做不是。让我们从Mul开始。

class Mul a b c | a b -> c where
    (|*|) :: a -> b -> c

没有扩展,类型类只包含一个类型参数,但使用MultiParamTypeClasses,我们可以为abc 类型的组合声明一个类型类,如Mul。参数之后的部分,| a b -> c 是一个“功能依赖”,在这种情况下,类型 c 依赖于 ab。这意味着如果我们有一个像Mul Double Point3D Point3D 这样的实例,函数依赖声明我们不能有任何其他实例Mul Double Point3D c,其中c 除了Point3D,即乘法的返回类型总是明确的由操作数的类型决定。

以下是我们为Mul 实现实例的方式:

instance Mul Double Double Double where
    (|*|) = (*)

instance Mul Point3D Double Point3D where
    Point3D x y z |*| a = Point3D (x*a) (y*a) (z*a)

instance Mul Double Point3D Point3D where
    a |*| Point3D x y z = Point3D (x*a) (y*a) (z*a)

但是,这种灵活性并非没有注意事项,因为它会使编译器的类型推断变得更加困难。例如,你不能简单地写

p = Point3D 1 2 3 |*| 5

因为文字 5 不一定是 Double 类型。它可以是任何Num n => n,并且完全有可能有人声明了像Mul Point3D Int Int 这样的行为完全不同的新实例。所以这意味着我们需要明确指定数字文字的类型。

p = Point3D 1 2 3 |*| (5 :: Double)

现在,如果我们不想定义新的操作数,而是希望从 Prelude 覆盖默认的 Num 类,我们可以这样做

import Prelude hiding (Num(..))
import qualified Prelude as P

class Mul a b c | a b -> c where
    (*) :: a -> b -> c

instance Mul Double Double Double where
    (*) = (P.*)

instance Mul Point3D Double Point3D where
    Point3D x y z * a = Point3D (x*a) (y*a) (z*a)

【讨论】:

    【解决方案2】:

    您可以使用可以采用不同类型的乘法来创建自定义类。

    import Prelude hiding ((*))
    import qualified Prelude
    
    class Mul a b c | a b -> c where (*) :: a -> b -> c
    instance Mul Double Double Double where (*) = (Prelude.*)
    instance Mul Double Int Double where a * b = a Prelude.* fromIntegral b
    ...
    

    您需要打开多参数类型类和功能依赖才能使其工作。

    【讨论】:

      【解决方案3】:

      没有办法让标准的Num 函数(包括运算符)返回不同的类型。 * 的类型为 Num n => n -> n -> n,这意味着 n 必须始终是相同的类型。

      也无法让标准的Num 函数(如+)处理两种不同类型的参数。

      这个问题的通常解决方案是创建一个新的运算符。因此,您可以创建一个像 |+| 这样的标量加法运算符,并使用它为您的点添加双打。

      如果您不反对 unicode,您可以将 · 用于您的点积 :)。 Haskell 支持这一点,但其他程序可能难以输入 unicode。

      【讨论】:

      • 啊,是的,我创建了一个自定义类,但是我没有想到要做一个 -> Double -> a.
      • 我认为这解决了这两种情况,它只需要我为我遇到的每一种不同情况想出一个不同的运算符。很烦人。
      • 为什么 Haskell 不能简单地使用条件运算符?如果是 Double Point3D,则执行此操作。如果是 Point3D Point3D,则执行此操作。
      • 没那么快。例如,看一下 hmatrix 包。即,查看 运算符,它是矩阵乘法运算符。您可以在此处查看文档:hackage.haskell.org/packages/archive/hmatrix/0.13.1.0/doc/html/…。您可以在此处查看底部的代码:hackage.haskell.org/packages/archive/hmatrix/0.13.1.0/doc/html/…。他对它进行了 jerry-rigged 使得 起作用:Matrix Matrix、Vector Matrix 和 Matrix Vector。他一定是以某种方式抽象出来的,使这看起来成为可能。
      • 嗯,是的,他做到了。但是,他使用了一些我不熟悉的扩展,所以我在这里帮不了你太多。此外,您仍然需要定义自己的运算符(而不是使用 +*)。
      猜你喜欢
      • 2011-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-17
      相关资源
      最近更新 更多