6

私は次のデータ構造を持っています:

data TempUnit = Kelvin Float
              | Celcius Float
              | Fahrenheit Float

温度をケルビンから別の単位に変換する関数を実装したいと思います。リターン型ユニットを関数に渡すにはどうすればよいですか?

4

3 に答える 3

14

これを行う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ます。

于 2012-12-31T16:50:02.517 に答える
4

途中で役立つリファクタリングを提案させてください。

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 番目の引数でパターン マッチを行い、変換する単位を決定します。

于 2012-12-31T16:45:55.080 に答える
3

コードには 1 つのタイプしかなく、それはTempUnit. KelvinCelciusあり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を持ち、温度を単位と組み合わせた数値として表すアプローチは、はるかに合理的です。そうすれば、変換関数は単純にターゲットユニットを引数として取ります-ポリモーフィズムは関係ありません。

于 2012-12-31T16:55:42.170 に答える