我有以下数据结构:
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)
请注意,此方法不是类型安全的:尝试将Celsius 100
转换为fromKelvin
时会发生什么?(即fromKelvin toF(Celsius 100)
的值是多少?)
Kelvin
来处理。让我建议一种重构方法,可以帮助你更好地完成任务:
data Unit = Kelvin | Celcius | Fahrenheit
data Temp = Temp Unit Float
convert :: Temp -> Unit -> Temp
编辑:
如果你无法进行重构,那么你仍然可以做你想做的事情,只是稍微不太干净:
convert :: Temp -> Temp -> Temp
假设你想要将一个以Kelvin
为单位的温度(用标识符t
表示)转换成Celsius
,你可以按照以下步骤进行:
convert t (Celcius 0)
convert
实现将会通过匹配第二个参数来确定要进行转换的单位。在您的代码中只有一个类型,那就是 TempUnit
。 Kelvin
、Celcius
和Fahrenheit
不是类型,它们是数据构造器。因此,您无法使用多态来在它们之间进行选择。
如果您想要使用返回类型多态性,您需要定义三种不同的类型并将它们作为同一类型类的实例。这可能看起来像这样:
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
,将温度表示为数字和单位的组合。这样,转换函数只需要将目标单位作为参数即可 - 不涉及多态。