Lets say I’ve got the following code
type IsTall = Bool
type IsAlive = Bool
is_short_alive_person is_tall is_alive = (not is_tall) && is_alive
Say, later on, I’ve got the following
a :: IsAlive
a = False
b :: IsTall
b = True
And call the following, getting the two arguments around the wrong way:
is_short_alive_person a b
This successfully compiles unfortunately, and at runtime tall dead people are instead found instead of short alive people.
I would like the above example not to compile.
My first attempt was:
newtype IsAlive = IsAlive Bool
newtype IsTall = IsTall Bool
But then I can’t do something like.
switch_height :: IsTall -> IsTall
switch_height h = not h
As not is not defined on IsTalls, only Bools.
I could explicitly extract the Bools all the time, but that largely defeats the purpose.
Basically, I want IsTalls to interact with other IsTalls, just like they’re Bools, except they won’t interact with Bools and IsAlives without an explicit cast.
What’s the best way to achieve this.
p.s. I think I’ve achieved this with numbers by doing in GHC:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype UserID = UserID Int deriving (Eq, Ord, Num)
newtype GroupID = GroupID Int deriving (Eq, Ord, Num)
(i.e. UserID’s and GroupID’s shouldn’t interact)
but I can’t seem to do this with Bools (deriving Bool doesn’t work). I’m not even sure the above is the best approach anyway.
You can get some way towards this, using
newtypes and a class if you import the Prelude hiding the boolean functions you want to use with yourIsTallandIsAlivevalues. You redefine the boolean functions as methods in the class, for which you then make instances for all 3 of theBool,IsTall, andIsAlivetypes. If you useGeneralizedNewtypeDerivingyou can even get theIsTallandIsAliveinstances without having to write the wrapping/unwrapping boilerplate by hand.Here’s an example script I actually tried out in ghci:
You can now
&&,||, andnotvalues of any of the three types, but not together. And they’re separate types, so your function signatures can now restrict which of the 3 they want to accept.Higher order functions defined in other modules will work fine with this, as in:
But you won’t be able to pass an
IsTallto any other function defined elsewhere that expects aBool, because the other module will still be using the Prelude version of the boolean functions. Language constructs likeif ... then ... else ...will still be a problem too (although a comment by hammar on Norman Ramsey’s answer says you can fix this with another GHC extension). I’d probably add atoBoolmethod to that class to help uniformly convert back to regularBools to help mitigate such problems.