Haskell Return Polymorphism Type

I have the following data structure:

data TempUnit = Kelvin Float | Celcius Float | Fahrenheit Float 

I want to implement a function that converts temperature from Kelvin to another Unit. How to pass an element of the return type to a function?

+6
source share
3 answers

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 ).

+14
source

Let me suggest refactoring that can help you along the way:

 data Unit = Kelvin | Celcius | Fahrenheit data Temp = Temp Unit Float 

Then you can easily do what you want:

 convert :: Temp -> Unit -> Temp 

EDIT:

If you cannot do this refactoring, you can still do what you want, it is a little less clean:

 convert :: Temp -> Temp -> Temp 

Suppose you want to convert the temperature in Kelvin (the value Celcius with the identifier t ) to Celcius . You would do something like this:

 convert t (Celcius 0) 

Your implementation of convert matches the pattern in the second argument to determine the units to be converted.

+4
source

There is only one type in the code and TempUnit . Kelvin , Celcius and Fahrenheit are not types, they are data constructors. Therefore, you cannot use polymorphism to choose between them.

If you want to use a return type polymorphism, you will need to define 3 different types and make them instances of the same type. It might look something like this:

 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 

You can then choose which conversion you want by providing type annotations (or using the result in a context where a specific type is required):

 myTemp :: Celsius myTemp = fromKelvin $ Kelvin 42 

However, this does not seem to be a good use of polymorphism. The approach in which you have the algebraic data type TemperatureUnit , and then represents the temperature as a number in combination with a unit, seems much more reasonable. Thus, the transformation functions will simply take the target unit as an argument - polymorphism will not be involved.

+3
source

All Articles