2012-02-22 20 views
4

クラスインスタンスがインスタンスの型でない値を返すようにする方法はありますか?例では、2つのベクトルのスカラー積のための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)

+1

のような主要な努力を行うことができますclassは[Numeric Prelude](http://www.haskell.org/haskellwiki/Numeric_Prelude)です。 – leftaroundabout

答えて

6

Numの数値演算はすべてタイプが:: Num n => n -> n -> nで定義されているため、両方のオペランドと戻り値の型が同じでなければなりません。既存の型クラスを変更する方法はないので、新しい演算子を定義するか、または既存のNumクラスを非表示にして、独自の実装と完全に置き換えることができます。

異なるオペランドタイプを持つ演算子を実装するには、いくつかの言語拡張が必要です。その代わり+-*を含むNum様クラスの

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

Point3D * Doubleは理にかなっている間、Point3D + Doubleは通常ないので、それは、異なるオペランドのためのさまざまな型クラスを定義するために、より柔軟です。 Mulから始めましょう。拡張子なし

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

、型クラスは今まで、単一の型パラメータを含むが、MultiParamTypeClassesで、我々はタイプaの組み合わせについてMulbcなどの型クラスを宣言することができます。パラメータの後の部分、| a b -> cは、この場合、タイプcabに依存していることを示す「機能的依存」です。これは、Mul Double Point3D Point3Dのようなインスタンスを持つ場合、他のインスタンスを持つことができないということを意味します。Mul Double Point3D cPoint3D以外のもの、つまり、戻り値の型は常にオペランドの型によって明白に決まります。

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) 

を、それはコンパイラの型推論は、より多くの困難になりますので、この柔軟性は、しかし、その警告なしに来ない:ここでは

たちはMulのインスタンスを実装する方法を説明します。リテラル5は必ずしもタイプDoubleのではありませんのでたとえば、あなたは、単に

p = Point3D 1 2 3 |*| 5 

を書き込むことはできません。任意のNum n => nとすることができ、誰かが完全に異なる動作をするMul Point3D Int Intのような新しいインスタンスを宣言することは完全に可能です。つまり、数値リテラルの型を明示的に指定する必要があるということです。代わりに新しいオペランドを定義するので我々はPreludeからデフォルトNumクラスをオーバーライドしたい場合

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

今、私たちは `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

優れた応答。ありがとうございました。 –

5

標準のNum関数(演算子を含む)に異なる型を返す方法はありません。 *のタイプはNum n => n -> n -> nです。つまり、nは全体を通して同じタイプでなければなりません。

Numのような標準(+など)の関数は、2つの異なる型の引数で動作することもありません。

この問題の通常の解決策は、新しい演算子を作成することです。したがって、|+|のようなスカラー加算演算子を作成し、これを使用してポイントにダブルを追加することができます。

ユニコードに反対していない場合は、あなたのドットプロダクトには使用できます。 Haskellはこれをサポートしていますが、他のプログラムではUnicodeの入力が困難な場合があります。

+0

ああ、はい、私はカスタムクラスを作成しましたが、私はダブル→aをやっているとは思いませんでした。 –

+0

私はこれが両方の状況を修正したと思います。それは、私が持っている1つの状況ごとに異なる演算子が必要になるだけです。とてもうるさい。 –

+0

なぜ単純に条件演算子を持たないのでしょうか? Double Point3Dならば、これをしてください。 Point3D Point3Dの場合は、これを行います。 –

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

マルチパラメータ型のクラスと関数の依存関係を有効にする必要があります。