'How can i convince Idris that my function is covering?

I wrote a function doSomething that takes an open or closed parenthesis and returns a corresponding Int:

doSomething : (c : Char) -> {auto isPar : c == '(' || c == ')' = True} -> Int
doSomething '(' {isPar = Refl} = 1
doSomething ')' {isPar = Refl} = -1

With isPar i want to make sure that only parentheses and no other characters can get passed to doSomething. But Idris complains:

Error: doSomething is not covering
Missing cases:
    doSomething _

How can i convince or proof to Idris that all possible cases are covered? I tried:

doSomething 'a' {isPar = Refl} impossible --this type checks
doSomething _   {isPar = Refl} impossible --this not


Solution 1:[1]

The compiler can't guess that ( and ) are the only values that allow the construction of an isPar values, and thus is not able to conclude that the remaining case is impossible.

A workaround is to keep track that something is not an open or a clos parenthesis. It can be done like so:

import Data.So

doSomething : (c : Char) -> {auto isPar : So (c == '(' || c == ')')} -> Int
doSomething x {isPar} = case (choose (x == '(')) of
  Left _ => 1
  Right notOpen => case (choose (x == ')')) of
    Left _ => -1
    Right notClose => case (soOr isPar) of
        Left isOpen => absurd $ soNotToNotSo notOpen isOpen
        Right isClose => absurd $ soNotToNotSo notClose isClose

So is a proof that a predicate is True, choose provides either a proof that a predicate is True (the left branch), or a proof that it isn't true (the right branch). Using the proof that x is neither ( (notOpen) nor ) (notClose), we can conclude by analysing why isPar is true (with soOr).

You can find the type of all the functions I used to deal with So in its package.

Solution 2:[2]

We can use a with expression to obtain an implementation that I think is a bit more elegant than using So, which I try to reserve only as a last resort.

||| Test if a Char is an open or close paren.
isParen : Char -> Bool
isParen '(' = True
isParen ')' = True
isParen _ = False

||| Obtain the integer code corresponding to a paren.
||| Proof is required that c is '(' or ')'.
parenCode :
  (c : Char) ->
  {auto 0 prf_isParen : isParen c === True}
  Integer
parenCode c with (isParen c)
  parenCode '(' | True = 1
  parenCode ')' | True = -1
  parenCode _   | False impossible

If we perform the comparison inside the with, the compiler is able to recognize isParen c as identical to the expression as in our prf_isParen.

This generates Scheme code that you would hope for:

(define Misc-isParen
  (lambda (arg-0)
    (cond ((equal? arg-0 #\() 1)
          ((equal? arg-0 #\)) 1)
          (else 0))))

(define Misc-with--parenCode-8826
  (lambda (arg-0 arg-1 arg-2)
    (cond
      ((equal? arg-0 #\() (cond (else 1)))
      (else (cond (else -1))))))

(define Misc-parenCode
  (lambda (arg-0)
    (Misc-with--parenCode-8826 arg-0
                               (Misc-isParen arg-0)
                               'erased)))

Although the with generates an extra function call, it only invokes one character comparison.

Also, the Chez compiler is smart enough to optimize away these nested functions:

(expand/optimize
  '(let ((Misc-isParen
           (lambda (arg-0)
             (cond ((equal? arg-0 #\() 1)
                   ((equal? arg-0 #\)) 1)
                   (else 0))))
         (Misc-with--parenCode-8826
           (lambda (arg-0 arg-1 arg-2)
             (cond ((equal? arg-0 #\() (cond (else 1)))
                   (else (cond (else -1))))))
         (Misc-parenCode
           (lambda (arg-0)
             (Misc-with--parenCode-8826 arg-0
                                        (Misc-isParen arg-0)
                                        'erased))))
  (Misc-parenCode #\()))
(Misc-with--parenCode-8826 #\( (Misc-isParen #\() 'erased)

You can also use with in this manner to construct the required proof that isParen c === True, by using the extra proof binding:

parenCodeTry : (c : Char) -> Maybe Integer
parenCodeTry c with (isParen c) proof prf_isParen
  _ | False = Nothing
  _ | True = Just $ parenCode c

In the True branch of parenCodeTry, the type of prf_isParen will be:

prf_isParen : Equal (isParen c) True

which will allow the compiler to find a value for the auto 0 prf_isParen parameter.

In the False branch of parenCodeTry, it will be:

prf_isParen : isParen c = False

which of course is not a valid value for the auto 0 prf_isParen parameter, and therefore you would not be able to invoke parenCode in that branch.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2