Haskell自定义数学类型和类

Xan*_*unn 4 math haskell types class operator-overloading

有没有办法让类实例返回一个不属于实例类型的值?一个例子是想要为两个向量的标量积返回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
Run Code Online (Sandbox Code Playgroud)

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

sha*_*ang 6

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

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

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
Run Code Online (Sandbox Code Playgroud)

而不是包含类似Num的类+,-并且*,它更灵活地为不同的操作数定义不同的类型类,因为虽然Point3D * Double有意义,但Point3D + Double通常不会.让我们开始吧Mul.

class Mul a b c | a b -> c where
    (|*|) :: a -> b -> c
Run Code Online (Sandbox Code Playgroud)

没有扩展,类型类只包含一个类型参数,但是MultiParamTypeClasses,我们可以声明类型Mul组合的类型类a,bc.参数之后的部分| 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)
Run Code Online (Sandbox Code Playgroud)

然而,这种灵活性并非没有它的警告,因为它会使编译器的类型推断变得更加困难.例如,你不能简单地写

p = Point3D 1 2 3 |*| 5
Run Code Online (Sandbox Code Playgroud)

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

p = Point3D 1 2 3 |*| (5 :: Double)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)


Tik*_*vis 5

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

也没有办法让标准Num函数(比如+)使用两种不同类型的参数.

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

如果你不反对unicode,你可以使用·作为你的点积:).Haskell支持这一点,但其他程序可能难以键入unicode.