2012-02-22 128 views
4

有什麼辦法讓類實例返回一個不是實例類型的值嗎?一個例子是想返回雙人間兩個向量的標量積類型的值:Haskell自定義數學類型和類

-- 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)

+1

主要努力克服'Num'的問題類是[數字前奏](http://www.haskell.org/haskellwiki/Numeric_Prelude)。 – leftaroundabout 2012-02-22 15:30:10

回答

6

類型類型Num中的數字操作全部用:: Num n => n -> n -> n類型定義,所以操作數和返回值必須具有相同的類型。沒有辦法改變現有的類型類別,所以你的選擇是要麼定義新的操作符,要麼隱藏現有的類,並用你自己的實現來完全替代它。

爲了實現具有不同操作數類型的操作符,您將需要一些語言擴展。

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

而是涵蓋+-*一個Num狀類的,它更靈活地定義不同類型類爲不同的操作數,因爲在Point3D * Double有道理,Point3D + Double通常不會。我們從Mul開始。

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

沒有擴展,類型類永遠只含有單一類型的參數,但MultiParamTypeClasses,我們可以聲明一個類型類像Mul的類型abc組合。參數| 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) 
+0

非常好的迴應。謝謝。 – 2012-02-22 20:47:20

5

沒有辦法讓標準Num函數(包括運算符)返回不同的類型。 *的類型爲Num n => n -> n -> n,這意味着n必須始終是相同的類型。

也沒有辦法使標準Num函數(如+)與兩個不同類型的參數一起工作。

這個問題的通常解決方案是創建一個新的操作符。因此,您可以創建一個標量添加運算符,如|+|,並使用該運算符爲您的要點添加雙精度。

如果你不反對unicode,你可以使用·爲你的點積:)。 Haskell支持這一點,但其他程序可能難以輸入unicode。

+0

啊,是的,我創建了一個自定義類,但我沒有想到要做一個 - > Double - > a。 – 2012-02-22 02:39:17

+0

我認爲這解決了這兩種情況,它只是要求我爲每個不同的情況提出一個不同的操作符。很煩人。 – 2012-02-22 02:44:55

+0

爲什麼Haskell不能簡單地擁有條件運算符?如果Double Point3D,那麼這樣做。如果Point3D Point3D,然後做到這一點。 – 2012-02-22 02:49:38

4

您可以使用可以採用不同類型的乘法來創建自定義類。

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 
... 

您需要打開多參數類型類和函數依賴項才能工作。