Haskell返回类型多态性

Sve*_*enK 6 haskell

我有以下数据结构:

data TempUnit = Kelvin Float
              | Celcius Float
              | Fahrenheit Float
Run Code Online (Sandbox Code Playgroud)

我想实现一个将温度从开尔文转换为另一个单位的函数.如何将返回类型单元传递给函数?

huo*_*uon 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
Run Code Online (Sandbox Code Playgroud)

现在你可以使用toKelvin/ fromKelvin并根据(推断的)返回类型选择适当的实现,例如

absoluteZeroInF :: Fahrenheit 
absoluteZeroInF = fromKelvin (Kelvin 0)
Run Code Online (Sandbox Code Playgroud)

(注意使用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 (...)
Run Code Online (Sandbox Code Playgroud)

(这可能是由@seliopou建议的方法做得更好:打破一个单独的Unit类型.)

这可以这样使用:

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

fromKelvin toC (Kelvin 10)
fromKelvin toF (Kelvin 10000)
Run Code Online (Sandbox Code Playgroud)

请注意,这种方法是不是类型安全:尝试转换时,会发生什么Celcius 100fromKelvin?(即什么值fromKelvin toF (Celcius 100)?)


所有这些说,最好是在一个单元内部标准化,只在输入和输出上转换为其他单元,即只有读取或写入温度的函数需要担心转换,其他一切只能用于(例如)Kelvin.