我有以下数据结构:
data TempUnit = Kelvin Float
| Celcius Float
| Fahrenheit Float
我想实现一个将温度从开尔文转换为另一个单位的函数。如何将返回类型单元传递给函数?
答案 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 -> 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)
请注意,此方法不类型安全:尝试使用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)
您的代码中只有一种类型,即TempUnit
。 Kelvin
,Celcius
和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
然后将温度表示为与单位组合的数字的方法似乎更合理。这样转换函数就会将目标单元作为参数 - 不涉及多态性。