2012-12-31 36 views
6

我有以下數據結構:哈斯克爾返回類型多態性

data TempUnit = Kelvin Float 
       | Celcius Float 
       | Fahrenheit Float 

我想要實現從開爾文溫度轉換到另一個單位的功能。我如何將返回類型單位傳遞給函數?

回答

14

這樣做的一種方法是對於不同的溫度單位使用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 -> bconvert = 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 100fromKelvin轉換時會發生什麼? (即什麼是fromKelvin toF (Celcius 100)價值?)


這一切都表示,這將是最好的一個單元的內部規範,只轉換爲其他的輸入和輸出,即只讀取或寫入的溫度所需要的功能擔心轉換,其他所有內容僅適用於(例如)Kelvin

+1

@SvenK這是一個很好的解決方案,你可能會得到。如果你可以重構自己的代碼來匹配這種模式,你應該這樣做。 – seliopou

+1

我原則上同意,但我必須強烈反對開爾文量表的未被稱呼的特權。您應該根據唯一合理的比例[Rankine比例](http://en.wikipedia.org/wiki/Rankine_scale)進行轉換。 –

+0

謝謝大家的最佳答案。 @dbaupp這不是生產代碼,我在一些關於我在大學學習的函數式編程的練習上工作,教我自己一些haskell。新年快樂。 – SvenK

4

讓我提出一個重構,可以幫助你走好人生路:

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

然後,你可以很容易地做你想做的事:

convert :: Temp -> Unit -> Temp 

編輯:

如果你不能執行重構,那麼你仍然可以做你想做的事情,它只是少一點乾淨:

convert :: Temp -> Temp -> Temp 

說你想要的溫度轉換到CelciusKelvin(綁定到標識符t值)。你會做這樣的事情:

convert t (Celcius 0) 

你實現convert將在第二個參數模式匹配來確定單位轉換爲。

3

代碼中只有一種類型,即TempUnitKelvin,CelciusFahrenheit不是類型,它們是數據構造函數。所以你不能用多態性來選擇它們。

如果你想使用返回類型多態,你需要定義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,然後用一個單位結合一個數字來表示溫度,這似乎更合理。這種方式轉換功能將簡單地將目標單元作爲參數 - 不涉及多態性。