2012-12-31 22 views
6

私は次のようなデータ構造を有する:Haskellの戻り値の型多型

data TempUnit = Kelvin Float 
       | Celcius Float 
       | Fahrenheit Float 

を私は別のユニットにケルビンの温度を変換する関数を実装したいです。戻り値の型のユニットを関数に渡すにはどうすればよいですか?

data Unit = Kelvin | Celcius | Fahrenheit 
data Temp = Temp Unit Float 

は、その後、あなたが簡単にあなたがやりたいことができます::

答えて

14

これを行う1つの方法は、異なる温度単位に対して3つの別々のタイプを使用し、温度としてそれらを「統合」するためにタイプクラスを使用することです。

今例えば、あなただけの toKelvin/ fromKelvinを使用することができますし、適切な実装が(推論)戻り値の型に基づいて選択されます
newtype Kelvin = Kelvin Float 
newtype Celcius = Celcius Float 
newtype Fahrenheit = Fahrenheit Float 

class TempUnit a where 
    fromKelvin :: Kelvin -> a 
    toKelvin :: a -> Kelvin 

instance TempUnit Kelvin where 
    fromKelvin = id 
    toKelvin = id 

instance TempUnit Celcius where 
    fromKelvin (Kelvin k) = Celcius (k - 273.15) 
    toKelvin (Celcius c) = Kelvin (c + 273.15) 

instance TempUnit Fahrenheit where 
    fromKelvin (Kelvin k) = Fahrenheit ((k-273.15)*1.8 + 32) 
    toKelvin (Fahrenheit f) = Kelvin ((f - 32)/1.8 + 273.15 

newtypeなくdataの使用に注意してください、これはdataと同じであるが、余分なコンストラクタのランタイムコストなしで)

absoluteZeroInF :: Fahrenheit 
absoluteZeroInF = fromKelvin (Kelvin 0) 

この方法は、自動的に、任意の変換関数convert :: (TempUnit a, TempUnit b) => a -> b提供する:convert = fromKelvin . toKelvinを。その点では、TempUnitではなく、TempUnit a => ... aという制約で任意の温度を処理する関数の型シグネチャを記述する必要があります。


「センチネル」値は、それ以外の場合は無視されます。

fromKelvin :: TempUnit -> TempUnit -> TempUnit 
fromKelvin (Kelvin _) (Kelvin k) = Kelvin k 
fromKelvin (Celcius _) (Kelvin k) = Celcius (k - 273.15) 
fromKelvin (Fahrenheit _) (Kelvin k) = Fahrenheit (...) 

(これはおそらくより良い示唆@seliopou方法で行われます。別のUnitタイプを壊す)

これがそうのように使用することができます。この方法という

-- aliases for convenience 
toC = Celcius 0 
toK = Kelvin 0 
toF = Fahrenheit 0 

fromKelvin toC (Kelvin 10) 
fromKelvin toF (Kelvin 10000) 

注意ではないタイプセーフ:をfromKelvinに変換しようとするとどうなりますか? (すなわち、fromKelvin toF (Celcius 100)の値は何ですか?)


このすべては言った、それは内部的に一つのユニットを標準とだけ入力と出力に他の人に変換し、読みつまりのみの機能や温度が必要と書くことが最善だろうコンバージョンを心配するには、他のすべてがちょうど(例えば)Kelvinで動作します。

+1

@SvenKこれはあなたが得る可能性のあるソリューションと同じくらい良いです。このパターンと一致するように独自のコードをリファクタリングできる場合は、そうする必要があります。 – seliopou

+1

私は原則として同意しますが、私はケルビンスケールの特権を失うことに強く反対しなければなりません。あなたは、[Rankine scale](http://en.wikipedia.org/wiki/Rankine_scale)という賢明な尺度に基づいて変換を行う必要があります。 –

+0

偉大な答えをお寄せいただきありがとうございます。 @dbauppこれはプロダクションコードではありません。私は大学での自分の研究の機能プログラミングに関するいくつかの練習問題に取り組んでいます。明けましておめでとうございます。 – SvenK

4

私はあなたの道に沿ってあなたを助けるかもしれリファクタリングを示唆してみましょう

convert :: Temp -> Unit -> Temp 

EDIT:

リファクタリングを実行できない場合は、やりたいことをやりなおすことができます。ちょっと清潔です。

convert :: Temp -> Temp -> Temp 
あなたは CelciusKelvin(識別子 tにバインドされた値)で温度を変換してみたいと思います。あなたはこのような何かをしたい:

convert t (Celcius 0) 

convertの実装では、第二引数のパターンマッチをに変換するユニットを決定するだろう。

3

コードには1種類しかありません。それはTempUnitです。 Kelvin,CelciusおよびFahrenheitは型ではなく、データコンストラクタです。したがって、多態性を使用してそれらの間で選択することはできません。

戻り型多型を使用する場合は、3つの異なる型を定義し、それらを同じ型のクラスのインスタンスにする必要があります。つまり、このようなものになります:

newtype Kelvin = Kelvin Float 
newtype Celsius = Celsius Float 
newtype Fahrenheit = Fahrenheit Float 

class Temperature t where 
    fromKelvin :: Kelvin -> t 
    toKelvin :: t -> Kelvin 

instance Temperature Kelvin where 
    fromKelvin = id 
    toKelvin = id 

instance Temperature Celsius where 
    fromKelvin (Kelvin k) = Celsius $ -- insert formula here 
    toKelvin (Celsius c) = Kelvin $ -- insert formula here 

instance Temperature Fahrenheit where 
    -- same as for Celsius 

あなたは、あなたが(特定のタイプが必要とされているコンテキストにまたは結果を使用して)型注釈を供給することによってたい変換を選択できます。

myTemp :: Celsius 
myTemp = fromKelvin $ Kelvin 42 

しかし、これは多形性をよく使うようには見えません。代数的データ型がTemperatureUnitで、ユニットと組み合わせた数値として温度を表す手法は、はるかに合理的です。そのように変換関数は引数としてターゲットユニットを取るだけで、多態性は関与しません。