Я бы порекомендовал вам избегать наличия трех разных возможных представлений температуры.Это просто приводит к большому количеству ветвлений и преобразований во время выполнения.Имеет смысл иметь выделенный тип для температур, и имеет смысл оставить шкалу, использовавшую частную реализацию, но придерживаться одного соглашения упрощает вещи.
module Physics.Quantities.Temperature (Temperature) where
newtype Temp = KelvinTemp { getKelvinTemperature :: Double }
deriving (Eq, Ord)
Обратите внимание, что я не экспортируюспецифичный для Кельвина конструктор, поэтому для любого, кто использует этот тип, не может иметь значение, какая шкала температуры используется.А поскольку внутреннее представление является фиксированным, компилятор может самостоятельно определять экземпляры Eq
и Ord
.
Теперь все в порядке, очевидно, вам все равно нужно будет на самом деле получить материалсделано , поэтому вам понадобятся средства доступа.Одним из способов является простое считывание в этом масштабе, например
toCelsius :: Temp -> Double
toCelsius (KelvinTemp tK) = tK - waterTriplePointInK
Но это будет односторонний процесс, не позволяющий создавать значения температуры снова.Элегантный способ достижения этого - использовать двунаправленные функции - изоморфизмы.Наиболее популярным представлением является библиотека из объектива :
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
...
...
Если вы действительно серьезно относитесь к этому, посмотрите пакет unit ,который делает все это и многое другое.