Haskell返回类型多态性

时间:2012-12-31 16:35:34

标签: haskell

我有以下数据结构:

data TempUnit = Kelvin Float
              | Celcius Float
              | Fahrenheit Float

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

3 个答案:

答案 0 :(得分: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 100转换fromKelvin时会发生什么? (即fromKelvin toF (Celcius 100)的价值是多少?)


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

答案 1 :(得分: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 :(得分:3)

您的代码中只有一种类型,即TempUnitKelvinCelciusFahrenheit不是类型,它们是数据构造函数。所以你不能使用多态来在它们之间进行选择。

如果要使用返回类型多态,则需要定义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然后将温度表示为与单位组合的数字的方法似乎更合理。这样转换函数就会将目标单元作为参数 - 不涉及多态性。