私は次のデータ構造を持っています:
data TempUnit = Kelvin Float
| Celcius Float
| Fahrenheit Float
温度をケルビンから別の単位に変換する関数を実装したいと思います。リターン型ユニットを関数に渡すにはどうすればよいですか?
私は次のデータ構造を持っています:
data TempUnit = Kelvin Float
| Celcius Float
| Fahrenheit Float
温度をケルビンから別の単位に変換する関数を実装したいと思います。リターン型ユニットを関数に渡すにはどうすればよいですか?
これを行う1つの方法は、異なる温度単位に3つの別々のタイプを使用し、タイプクラスを使用してそれらを温度として「結合」することです。
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
toKelvin
/を使用するだけfromKelvin
で、(推測された) 戻り値の型に基づいて適切な実装が選択されます。
absoluteZeroInF :: Fahrenheit
absoluteZeroInF = fromKelvin (Kelvin 0)
newtype
(ではなくの使用に注意してくださいdata
。これは と同じですdata
が、追加のコンストラクターの実行時コストはありません。)
このメソッドは、任意の変換関数をconvert :: (TempUnit a, TempUnit b) => a -> b
自動的に提供します: convert = fromKelvin . toKelvin
. その点で、これにはTempUnit a => ... a
、プレーンなTempUnit
.
それ以外の場合は無視される「センチネル」値を使用することもできます。
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)
このメソッドはタイプ セーフではないCelcius 100
ことに注意してくださいfromKelvin
。(つまり、 の値はfromKelvin toF (Celcius 100)
何ですか?)
つまり、温度を読み書きする関数だけが変換を心配する必要があり、他のすべては (eg) で動作しKelvin
ます。
途中で役立つリファクタリングを提案させてください。
data Unit = Kelvin | Celcius | Fahrenheit
data Temp = Temp Unit Float
次に、やりたいことを簡単に実行できます。
convert :: Temp -> Unit -> Temp
編集:
そのリファクタリングを実行できない場合でも、やりたいことを実行できます。
convert :: Temp -> Temp -> Temp
Kelvin
(識別子 にバインドされた値t
)の温度を に変換したいとしCelcius
ます。次のようにします。
convert t (Celcius 0)
の実装はconvert
、2 番目の引数でパターン マッチを行い、変換する単位を決定します。
コードには 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
を持ち、温度を単位と組み合わせた数値として表すアプローチは、はるかに合理的です。そうすれば、変換関数は単純にターゲットユニットを引数として取ります-ポリモーフィズムは関係ありません。