Ruby has a nice feature that allows to convert numbers to other things, e.g. 3.times
for iteration or 3.to_s
for converting it to a string.
People say Haskell is good for writing natural DSLs.
Is it possible to write units as postfixes, e.g. timeout = 3 seconds
?
Yes.
You can do this with the following simple trick:
{-# LANGUAGE FlexibleInstances #-}
instance Num (Integer -> Integer) where
fromInteger n = \scale -> n * scale -- return a function that takes
-- a number and returns a number
Then you can write:
seconds, minutes, hours, days :: Integer
seconds = 1000000 -- base unit, e.g. microseconds
minutes = 60 seconds
hours = 60 minutes
days = 24 hours
soon :: Integer
soon = 2 hours + 4 seconds
How does this work?
Above we have given a Num
instance for Integer -> Integer
, that is for a function that takes an integer and returns an integer.
Every type that implements Num
and has its function fromInteger
defined is allowed to be represented by a numeric literal, e.g. 3
.
This means that we can write 3 :: Integer -> Integer
- here 3
is a function that takes an integer and returns an integer!
Therefore, we can apply an integer to it, for example seconds
; we can write 3 seconds
and the expression will be of type Integer
.
A more type-safe version
In fact, we could even write 3 (3 :: Integer)
now - this probably doesn't make much sense though. We can restrict this by making it more type-safe:
newtype TimeUnit = TimeUnit Integer
deriving (Eq, Show, Num)
instance Num (TimeUnit -> TimeUnit) where
fromInteger n = \(TimeUnit scale) -> TimeUnit (n * scale)
seconds, minutes, hours, days :: TimeUnit
seconds = TimeUnit 1000000
minutes = 60 seconds
hours = 60 minutes
days = 24 hours
Now we can only apply things of type TimeUnit
to number literals.
You could do that for all kinds of other units, such as weights or distances or people.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With