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)
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,b和c.参数之后的部分| a b -> c是"功能c依赖性",在这种情况下表明类型依赖于a和b.这意味着如果我们有一个类似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)
现在,如果不是定义新的操作数而是希望覆盖默认Num类Prelude,我们就可以这样做
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)
没有办法让标准Num函数(包括运算符)返回不同的类型.*具有的类型Num n => n -> n -> n意味着n必须始终是相同的类型.
也没有办法让标准Num函数(比如+)使用两种不同类型的参数.
这个问题的通常解决方案是创建一个新的运算符.因此,您可以创建一个标量加法运算符,|+|并使用它来为您的点添加双精度.
如果你不反对unicode,你可以使用·作为你的点积:).Haskell支持这一点,但其他程序可能难以键入unicode.