One way to do this is to use 3 separate types for different temperature units, and then use a type class to βcombineβ them as temperatures, for example
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
Now you can simply use toKelvin / fromKelvin , and the appropriate implementation will be selected based on the return type (intended), for example.
absoluteZeroInF :: Fahrenheit absoluteZeroInF = fromKelvin (Kelvin 0)
(Note the use of newtype , not data , is the same as data , but without the cost of executing an additional constructor.)
This method automatically provides an arbitrary conversion function convert :: (TempUnit a, TempUnit b) => a -> b : convert = fromKelvin . toKelvin convert = fromKelvin . toKelvin . In this case, this requires writing function type signatures that handle arbitrary temperatures using the constraints TempUnit a => ... a , and not just TempUnit .
You can also use the sentinel value, which is otherwise ignored, for example.
fromKelvin :: TempUnit -> TempUnit -> TempUnit fromKelvin (Kelvin _) (Kelvin k) = Kelvin k fromKelvin (Celcius _) (Kelvin k) = Celcius (k - 273.15) fromKelvin (Fahrenheit _) (Kelvin k) = Fahrenheit (...)
(This is probably best done using the @seliopou method, suggesting: break up a separate type of Unit .)
This can be used like this:
-- aliases for convenience toC = Celcius 0 toK = Kelvin 0 toF = Fahrenheit 0 fromKelvin toC (Kelvin 10) fromKelvin toF (Kelvin 10000)
Note that this method is not type safe: what happens when I try to convert Celcius 100 using fromKelvin ? (i.e. what is the value fromKelvin toF (Celcius 100) ?)
All this suggests that it would be better to standardize internally on one device and convert only other elements at the input and output, i.e. only functions that read or write temperatures need to worry about conversions, everything else just works (e.g. Kelvin ).