Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Limit a number to a range (Haskell)

I am exposing a function which takes two parameters, one is a minimum bound and the other is a maximum bound. How can I ensure, using types, that for example the minimum bound is not greater than the maximum bound?

I want to avoid creating a smart constructor and returning a Maybe because it would make the whole usage more cumbersome.

Thank you

like image 963
doodledood Avatar asked Oct 01 '16 18:10

doodledood


3 Answers

This doesn't exactly answer your question, but one approach that sometimes works is to change your interpretation of your type. For example, instead of

data Range = {lo :: Integer, hi :: Integer}

you could use

data Range = {lo :: Integer, size :: Natural}

This way, there's no way to represent an invalid range.

like image 106
dfeuer Avatar answered Nov 20 '22 14:11

dfeuer


This solution uses dependent types (and might be too heavyweight, check if dfeuer's answer is enough for your needs).

The solution makes use of the GHC.TypeLits module from base, and also of the typelits-witnesses package.

Here is a difference function that takes two integer arguments (known statically) and complains at compile-time when the first number is greater than the second:

{-# language TypeFamilies #-}
{-# language TypeOperators #-}
{-# language DataKinds #-}
{-# language ScopedTypeVariables #-}

import GHC.TypeLits
import GHC.TypeLits.Compare
import Data.Type.Equality
import Data.Proxy
import Control.Applicative

difference :: forall n m. (KnownNat n,KnownNat m,n <= m) 
           => Proxy n 
           -> Proxy m 
           -> Integer
difference pn pm = natVal pm - natVal pn

We can check it from GHCi:

ghci> import Data.Proxy
ghci> :set -XTypeApplications
ghci> :set -XDataKinds
ghci> difference (Proxy @2) (Proxy @7)
5
ghci> difference (Proxy @7) (Proxy @2)
** TYPE ERROR **

But what if we want to use the function with values determined at run time? Say, values that we read from console, or from a file?

main :: IO ()
main = do
   case (,) <$> someNatVal 2 <*> someNatVal 7 of
       Just (SomeNat proxyn,SomeNat proxym) ->
            case isLE proxyn proxym of
                Just Refl -> print $ difference proxyn proxym 
                Nothing   -> error "first number not less or equal"
       Nothing ->     
            error "could not bring KnownNat into scope"

In this case, we use functions like someNatVal and isLE. These functions might fail with Nothing. If they succeed, however, they return a value that "witnesses" some constraint. And by pattern-matching on the witness, we bring that constraint into scope (this works because the witness is a GADT).

In the example, the Just (SomeNat proxyn,SomeNat proxym) pattern match brings KnownNat constraints for the two arguments into scope. And the Just Refl pattern match brings the n <= m constraint into scope. Only then we can call our difference function.

So, in a way, we have shifted all the busywork of ensuring that the arguments satisfy the required preconditions out of the function itself.

like image 10
danidiaz Avatar answered Nov 20 '22 14:11

danidiaz


What you're asking for is dependent types. There is a nice tutorial on it in https://www.schoolofhaskell.com/user/konn/prove-your-haskell-for-great-safety/dependent-types-in-haskell

Although I don't know how friendly it will be. Do note that dependent typing was improved in GHC 8.0 but I have no experience in that area. I would make sure you're comfortable using template Haskell if you don't want it to be cumbersome.

like image 1
EvHi Avatar answered Nov 20 '22 14:11

EvHi