我具有以下表示温度的数据声明:
data Temp = Kelvin Float | Celsius Float | Fahrenheit Float deriving Show
-- Functions for conversion between temperatures
kelvToCels :: Temp -> Temp
kelvToCels (Kelvin k) = Celsius (k-273.15)
kelvToFahr :: Temp -> Temp
kelvToFahr (Kelvin k) = Fahrenheit (((9/5)*(k-273.15))+32)
celsToKelv :: Temp -> Temp
celsToKelv (Celsius c) = Kelvin (c+273.15)
celsToFahr :: Temp -> Temp
celsToFahr (Celsius c) = Fahrenheit (((9/5)*c)+32)
fahrToKelv :: Temp -> Temp
fahrToKelv (Fahrenheit f) = Kelvin ((5/9)*(f-32)+273.15)
fahrToCels :: Temp -> Temp
fahrToCels (Fahrenheit f) = Celsius ((f-32)/(9/5))
我希望能够比较温度,例如
> (Celsius 100) == (Fahrenheit 212.0)
计算结果为true。
这是我的尝试:
instance Eq Temp where
Celsius c == Fahrenheit f =
(celsToFahr c) == f
结果:ghci错误,因为RHS上的c和f是Floats而不是Temps,因此这里是一个“修复”:
instance Eq Temp where
Celsius c == Fahrenheit f =
(celsToFahr (Celsius c)) == (Fahrenheit f)
此编译没有错误,但是(Celsius 100) == (Fahrenheit 212.0)
引发异常:函数==
我还想创建一个Ord实例,以类似的方式重新定义compare
。
我已经走到了尽头,我找不到与我类似的任何示例,因此非常感谢任何建议。预先感谢。
答案 0 :(得分:11)
我建议您从不写不完整的模式匹配。考虑一下这对您的xToY
函数意味着什么,这意味着它们应该能够处理任何输入-因此它们的名称应更改为toY
。
我还要保证通过返回Float
(显然不能用错误的构造函数标记)而不是返回Temp
(可以)来知道使用哪个构造函数。所以:
toKelvin :: Temp -> Float
toKelvin (Fahrenheit f) = (5/9)*(f-32)+273.15
toKelvin (Celsius c) = c+273.15
toKelvin (Kelvin k) = k
与toCelsius
和toFahrenheit
类似。如果您确实想这样做,则可以分别编写类似的内容
normalizeKelvin :: Temp -> Temp
normalizeKelvin = Kelvin . toKelvin
但是这是否明智取决于您打算如何使用此代码。
鉴于此,我们现在可以编写一个Eq
实例,该实例不是递归的,只需选择其中一个比例作为自然比例并将其转换为 * 。所以:
instance Eq Temp where
t == t' = toKelvin t == toKelvin t'
请注意,在这里,当我们调用Temp
时,我们是从Float
实例调度到Eq
的{{1}}实例,这不同于您的代码是从{{ 1}}实例返回到对(==)
的{{1}}实例的另一个调用。
* 如果您对舍入有偏执,可以首先检查是否完全需要转换。所以:
Temp
答案 1 :(得分:0)
我建议您避免使用三种可能的温度表示形式。这只会导致大量的运行时分支和转换。拥有专用的温度类型确实是有意义的,并且使秤使用私有的实现细节是有意义的,但是坚持一个约定可以简化事情。
module Physics.Quantities.Temperature (Temperature) where
newtype Temp = KelvinTemp { getKelvinTemperature :: Double }
deriving (Eq, Ord)
请注意,我没有导出特定于Kelvin的构造函数,因此,对于使用此类型的任何人, 都不会影响使用哪种温度标度。并且由于内部表示形式是固定的,因此编译器可以自行找出Eq
和Ord
实例。
现在好了,显然您仍然需要能够完成工作,因此您将需要访问器。一种方法是像这样简单地按此比例读取
toCelsius :: Temp -> Double
toCelsius (KelvinTemp tK) = tK - waterTriplePointInK
但是那将是单向的,不允许您再次创建温度值。实现此目标的一种优雅方法是使用双向函数 –同构。最受欢迎的代表是lens library中的代表:
import Control.Lens
kelvin :: Iso' Temp Double
kelvin = iso getKelvinTemperature KelvinTemp
celsius :: Iso' Temp Double
celsius = iso (\(Temp tK) -> tK - waterTriplePointInK)
(\tC -> Temp $ tC + waterTriplePointInK)
where waterTriplePointInK = 273.15
fahrenheit :: Iso' Temp Double
fahrenheit = iso (\(Temp tK) -> (tK - fahrZeroInK)/fahrScaleFact)
(\tF -> Temp $ tF*fahrScaleFact + fahrZeroInK)
where fahrZeroInK = 255.372
fahrScaleFact = 5/9
现在您可以做类似的事情
*Main> let tBoil :: Temp; tBoil = 100^.from celsius
*Main> tBoil^.fahrenheit
212.00039999999993
*Main> 37^.from celsius.fahrenheit
98.60039999999992
*Main> 4000^.from kelvin.celsius
3726.85
如果您真的想针对不同的比例使用不同的表示形式,这是另一种类型更合理的方法,它将避免运行时分支:
{-# LANGUAGE DataKinds, KindSignatures, MultiParamTypeClasses #-}
data TemperatureScale = KelvinSc | CelsiusSc | FahrenheitSc
newtype KelvinTemperature = Kelvin {getKelvinTemperature ::Double}
newtype CelsiusTemperature = Celsius {getCelsiusTemperature ::Double}
newtype FahrenheitTemperature = Fahrenheit {getFahrenheitTemperature::Double}
type family Temperature (sc :: TemperatureScale) where
Temperature 'KelvinSc = KelvinTemperature
Temperature 'CelsiusSc = CelsiusTemperature
Temperature 'FahrenheitSc = FahrenheitTemperature
class ConvTemperature t t' where
convTemperature :: Temperature t -> Temperature t'
instance ConvTemperature KelvinSc KelvinSc where convTemperature = id
instance ConvTemperature CelsiusSc CelsiusSc where convTemperature = id
instance ConvTemperature FahrenheitSc FahrenheitSc where convTemperature = id
instance ConvTemperature KelvinSc FahrenheitSc where
...
...
如果您真的很认真,请查看units package,它可以完成所有这些以及更多工作。