我有以下数据结构:
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)
请注意,此方法不是类型安全的:尝试转换时会发生Celcius 100
什么fromKelvin
?(即 的值是fromKelvin toF (Celcius 100)
多少?)
综上所述,最好在一个单元上进行内部标准化,仅在输入和输出上转换为其他单元,即只有读取或写入温度的函数需要担心转换,其他一切都可以使用(例如)Kelvin
。
让我建议一个可以帮助你的重构:
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
将在第二个参数上进行模式匹配以确定要转换为的单位。
您的代码中只有一种类型,那就是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
然后将温度表示为与单位相结合的数字的方法似乎更合理。这样,转换函数将简单地将目标单元作为参数 - 不涉及多态性。