我有以下數據結構:哈斯克爾返回類型多態性
data TempUnit = Kelvin Float
| Celcius Float
| Fahrenheit Float
我想要實現從開爾文溫度轉換到另一個單位的功能。我如何將返回類型單位傳遞給函數?
我有以下數據結構:哈斯克爾返回類型多態性
data TempUnit = Kelvin Float
| Celcius Float
| Fahrenheit Float
我想要實現從開爾文溫度轉換到另一個單位的功能。我如何將返回類型單位傳遞給函數?
這樣做的一種方法是對於不同的溫度單位使用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)
價值?)
這一切都表示,這將是最好的一個單元的內部規範,只轉換爲其他的輸入和輸出,即只讀取或寫入的溫度所需要的功能擔心轉換,其他所有內容僅適用於(例如)Kelvin
。
讓我提出一個重構,可以幫助你走好人生路:
data Unit = Kelvin | Celcius | Fahrenheit
data Temp = Temp Unit Float
然後,你可以很容易地做你想做的事:
convert :: Temp -> Unit -> Temp
編輯:
如果你不能執行重構,那麼你仍然可以做你想做的事情,它只是少一點乾淨:
convert :: Temp -> Temp -> Temp
說你想要的溫度轉換到Celcius
Kelvin
(綁定到標識符t
值)。你會做這樣的事情:
convert t (Celcius 0)
你實現convert
將在第二個參數模式匹配來確定單位轉換爲。
代碼中只有一種類型,即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
,然後用一個單位結合一個數字來表示溫度,這似乎更合理。這種方式轉換功能將簡單地將目標單元作爲參數 - 不涉及多態性。
@SvenK這是一個很好的解決方案,你可能會得到。如果你可以重構自己的代碼來匹配這種模式,你應該這樣做。 – seliopou
我原則上同意,但我必須強烈反對開爾文量表的未被稱呼的特權。您應該根據唯一合理的比例[Rankine比例](http://en.wikipedia.org/wiki/Rankine_scale)進行轉換。 –
謝謝大家的最佳答案。 @dbaupp這不是生產代碼,我在一些關於我在大學學習的函數式編程的練習上工作,教我自己一些haskell。新年快樂。 – SvenK