@hackage typecheck-plugin-nat-simple0.1.0.2

Simple type check plugin which calculate addition, subtraction and less-or-equal-than

typecheck-plugin-nat-simple

what's this

This package provide plugin which extend type checking of Nat. The type checker can calculate only addition, subtraction and less or equal of constants and variables.

(View sample code on directory sample/.)

motivation

Suppose you need lengthed list. You define like following. (View sample/lengthed_tail.hs)

import GHC.TypeNats

infixr 6 :.

data List :: Nat -> * -> * where
        Nil :: List 0 a
	(:.) :: a -> List ln a -> List (ln + 1) a

And you want to define function tail.

tail_ :: List (n + 1) a -> List n a
tail_ Nil = error "tail_: Nil"
tail_ (_ :. xs) = xs

But it cause type check error.

error:
  ・Could not deduce: ln ~ n
    from the context: (n + 1) ~ (ln + 1)
    ...

Type checker say "I cannot derive (ln == n) from (n + 1 == ln + 1)". But it seems to be obvious. You can use this plugin like following.

{-# OPTIONS_GHC -fplugin=Plugin.TypeCheck.Nat.Simple #-}

Add it to top of the code, then type check succeed.

more example

To show more example, I will use Data.Proxy.Proxy. First examle is following (View sample/mplus1_nplus1.hs).

foo :: (m + 1) ~ (n + 1) => Proxy m -> Proxy n
foo = id

If you don't use this plugin, then following error occur.

  ・Could not deduce: m ~ n
    from the context: (m + 1) ~ (n + 1)
    ...

Use this plugin, you can compile it.

Second example is following (View sample/two_equations.hs).

foo :: ((x + y) ~ z, (w - y) ~ v) => Proxy (x + w) -> Proxy (z + v)
foo = id

Without this plugin, following error occur.

  ・Could not deduce: (x + w) ~ (z + v)
    from the context: ((x + y) ~ z, (w - y) ~ v)
    ...

Use this plugin, you can compile it.

error and recovery

type check error

See following code.

foo :: Proxy (n - 1 + 1) -> Proxy n
foo = id

This cause type check error even if you use this plugin.

  ・Couldn't match type `n' with `(n - 1) + 1'
  ...

research

What's wrong? You can see type check log of this plugin like following.

% stack ghc sample/minus1plus1.hs -- -ddump-tc-trace 2>&1 | grep -A 20 'Plugin.TypeCheck.Nat.Simple'
...
givens ([Exp v 'Boolean]): given (Givens v): (Givens [])
exBool: not boolean: (n_a1s2[sk:1] - 1) + 1
wanted (Exp v 'Boolean): ((((Var n_a1s2[sk:1]) :- (Const 1)) :+ (Const 1)) :== (Var n_a1s2[sk:1]))
wanted (Wanted v): (Wanted [(0 == 0), (1 * n_a1s2[sk:1] >= 0), (-1 + 1 * n_a1s2[sk:1] >= 0), (1 * n_a1s2[sk:1] >= 0)])
canDerive1: (0 == 0) is self-contained
canDerive1: (1 * n_a1s2[sk:1] >= 0) is self-contained
canDerive1: (-1 + 1 * n_a1s2[sk:1] >= 0) cannot be derived from
canDerive1: (1 * n_a1s2[sk:1] >= 0) is self-contained
result: type checker: return False
...

See the line of canDerive1: (- 1 + 1 * n_a1s2[sk:1] >= 0) cannot be derived from. It mean "-1 + n_a1s2[sk:1] should be greater or equal than 0. But no such context".

try calculation more portably

You can try to caluculate more simply.

% stack ghci
> :set -XTypeApplications
> :module Data.Derivation.Parse Data.Derivation.CanDerive Control.Monad.Try Data.Maybe
> parseConstraint "n - 1 + 1 == n"
Just ((:==) ((:+) ((:=) (Var "n") (Const 1)) (Const 1)) (Var "n"))
> Just w = wanted @String <$> it
> gs = givens @String []
> fst . runTry $ uncurry canDerive =<< (,) <$> gs <*> w
Right False

The wanted constraint cannot be derived from empty given constraint. Let's add 1 <= n constraint.

> gs = given @String . maybeToList $ parseConstraint "1 <= n"
> fst . runTry $ uncurry canDerive =<< (,) <$> gs <*> w
Right True

OK! It succeed if you add 1 <= n to given constraint.

recovery

Let's add 1 <= n context like following.

foo :: 1 <= n => Proxy (n - 1 + 1) -> Proxy n
foo = id

Then it succeed to type check.