Usage
In this module we’ll show how to use value-level dimensions, type-level dimensions and Quantity in an “actual” progam. Let’s first use this fancy GHC-extension
and then import the things we have created.
We don’t need to import Dimensions.ValueLevel since all its features are available by using the Quantity data type. The same thing could be said about Dimensions.TypeLevel. So why do we import it? It’s so we can write explicit type signatures, the trick of trade in Haskell progamming.
Values and types
Let’s create a length, time and mass, in the way hinted in the previous chapter.
Okay, let’s check out their type and value in GHCi.
ghci> myLength
10.0 m
ghci> :t myLength
myLength :: Quantity Length Double
ghci> myTime
20.0 s
ghci> :t myTime
myTime :: Quantity Time DoubleThe pretty printing works thanks to dimensions on value-level and the Haskell-types thanks to the type-level dimensions. Recall that the value-level dimensions just is a pretty simple “average” data type.
Let’s write some type signatures on those values (of type Quantity).
myLength :: Quantity Length Double
myLength = 10.0 # length
myTime :: Quantity Time Double
myTime = 20.0 # time
myMass :: Quantity Mass Double
myMass = 30.0 # massNeat! Length, Time and Mass are type-level dimensions. Therefore, Length is a type, just like String is a type.
Remember that the sugary style of writing 10.0 # length is a shorthand for doing
where length' refers to length in Dimensions.ValueLevel.
The reasons for not allowing this “raw” way of creating the value is to maintain the invariant of having the same value-level and type-level dimension. With the # function, the are forced by it to be the same. The following try to work around it is not possible.
* Couldn't match type 'Numeric.NumType.DK.Integers.Zero
with 'Numeric.NumType.DK.Integers.Pos1
Expected type: Quantity Length Double
Actual type: Quantity Time Double
* In the expression: 10.0 # time
In an equation for `myWeird': myWeird = 10.0 # timeCustom values and types
Creating values of a dimensions not exported is also possible. Let’s take energy as an example.
energy can now be used just as length and so on.
In order to write explicit type signatures, we can do like this
type Velocity = Length `Div` Time
type Acceleration = Velocity `Div` Time
type Force = Acceleration `Mul` Mass
type Energy = Force `Mul` LengthNote we had to introduce some helper types since, unlike the pre-made Quantity values, not as many were created for types.
Now energy can be defined and used like
energy :: Quantity Energy Double
energy = force *# length
energyToBoilMyGlassOfWater :: Quantity Energy Double
energyToBoilMyGlassOfWater = 12 # energySome functions
Let’s create a function operating on quantities. It should calculate the area of a rectangle given the lengths of its sides. The type should hence look like
areaOfRectangle :: Quantity Length Double -> Quantity Length Double ->
Quantity (Length `Mul` Length) DoubleAnd the actual function like
The values (width and height) can’t be pattern matched on. This is so we can maintain the invariant we keep nagging about. The side effect is that only the comparative and artithmetic operations exported by Dimensions.Quantity can be used.
To wrap up, let’s create a function to calculate the kinectic energy à la
\[\begin{align} E_k = \frac{m*v^2}{2} \end{align}\]