6

我有以下数据结构:

data TempUnit = Kelvin Float
              | Celcius Float
              | Fahrenheit Float

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

4

3 回答 3

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

于 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将在第二个参数上进行模式匹配以确定要转换为的单位。

于 2012-12-31T16:45:55.080 回答
3

您的代码中只有一种类型,那就是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 回答