Why isn't Idris able to typecheck the following code? - idris

I'm trying to see how "smart" the unification is in Idris. Type-checking fails in the following simple case:
GetBoolOrNat : Bool -> Type
GetBoolOrNat False = Bool
GetBoolOrNat True = Nat
Func : (b : Bool) -> (GetBoolOrNat b)
Func False = False
Func _ = Z
The error is:
|
75 | Func _ = Z
| ^
When checking right hand side of Func with expected type
GetBoolOrNat b
Type mismatch between
Nat (Type of 0)
and
GetBoolOrNat b (Expected type)
For a human it is obvious that in the last case, 'b' is 'True' and the function type is 'Bool -> Nat'. It works fine if you replace '_' with 'True'.
Can someone please explain why it doesn't work? Is this limitation related to the fact that full higher order unification is undecidable? I don't know much about higher order unification (I think Idris uses "Miller Unification").

Related

Why does the order of operands affect scope?

I'm trying to understand why a certain constructor is accepted in one expression but not another. I would have expected it to be out of scope in both. I'm a rank beginner to OCaml (I mostly use Haskell), so I could be missing something totally obvious to someone experienced.
type zero = Zero
type 'n succ = Succ
type 'n snat =
| SZero : zero snat
| SSucc : 'm snat -> 'm succ snat
module SimpleInduction (Pred : sig type 'n pred end) = struct
open Pred
type hyps =
{ base : zero pred
; step : 'm. 'm pred -> 'm succ pred}
let rec induct : type n. hyps -> n snat -> n pred =
fun h sn -> match sn with
| SZero -> h.base
| SSucc p -> h.step (induct h p)
end;;
let module Snot = struct type 'n pred = Top end in
let module Goop = SimpleInduction(Snot) in
Goop.induct {base = Top; step = fun _ -> Top} SZero = Top;;
(*
let module Snot = struct type 'n pred = Top end in
let module Goop = SimpleInduction(Snot) in
Top = Goop.induct {base = Top; step = fun _ -> Top} SZero;;
*)
This compiles just fine, for some reason. With the second definition of Snot uncommented, I get an error:
19 | Top = Goop.induct {base = Top; step = fun _ -> Top} SZero;;
^^^
Error: Unbound constructor Top
What brings Top into scope in the first definition of Snot? Using regular modules rather than first-classlocal ones makes no difference.
If I use Snot.Top on the left-hand side, I get no complaints on the right-hand side. Why is that?
In short, type-directed disambiguation is indeed not restricted to scope.
With an explicit type annotation, the type checker can select the constructor from the type without bringing the constructor in scope.
For instance,
module M = struct type 'a t = A of 'a end
let ok: _ M.t = A ()
let wrong: _ M.t = A (A ())
the first example is valid because the type annotation is enough to know that the A in A () is an _ A.t. However, the second example does not work because the constructor has not been brought into the scope.
Moreover, type-directed disambiguation only requires the expected type of the constructor or record to be known. Typically, in this example
let expected =
let f (M.A x) = x in
f (A ())
we know that the type of the argument of f is an _ M.t, thus we know that the A in f (A ()) come from _ M.t and we can use type-directed disambiguation like in the case with the explicit annotation.
If you find this behavior exotic, the warning 42 [name-out-of-scope] can be used to warn in such situation. Compiling your example with this warning yields (among many other instances of this warning)
23 | Goop.induct {base = Top; step = fun _ -> Top} SZero = Top
^^^
Warning 40 [name-out-of-scope]: Top was selected from type Snot.pred.
It is not visible in the current scope, and will not
be selected if the type becomes unknown.
(the warning names are new in 4.12)
Concerning your second point, the order of expression may matter in the absence of explicit annotations. Indeed, without explicit annotation, type-directed disambiguation will be only be able to select the right constructor when the expected type is already known. And type checking goes from left to right in OCaml. Thus in
... = Top
the type of the left-hand side has already been inferred and thus the expected type of Top is _ Snot.pred.
When the order is reversed
Top = ...
the typechecker is trying to find a constructor Top without any type information and there are no constructor Top in scope. Thus it fails with an Unbound constructor error. If you want to avoid depending on the order, you can either
write the full name of the constructor:
Snot.Top = ...
use an explicit type annotation
(Top: _ Snot.pred) = ...
open the Snot module.
Snot.( Top ) = ...
(* or *)
let open Snot in Top = ...
I would advise to use one of those solutions since there are more robust.
After all, relying on the specific implementation of the type checking is brittle.
In fact, there is a compiler flag -principal and a warning (18) [not-principal] that takes care to emit a warning in presence of such potentially brittle inference:
23 | Goop.induct {base = Top; step = fun _ -> Top} SZero = Top
^^^
Warning 18 [not-principal]: this type-based constructor disambiguation is not principal.
Here "not principal" means that the result of the type-based disambiguation depended on the order of the type-checking.

The signature for this packaged module couldn't be inferred in recursive function

I'm still trying to figure out how to split code when using mirage and it's myriad of first class modules.
I've put everything I need in a big ugly Context module, to avoid having to pass ten modules to all my functions, one is pain enough.
I have a function to receive commands over tcp :
let recvCmds (type a) (module Ctx : Context with type chan = a) nodeid chan = ...
After hours of trial and errors, I figured out that I needed to add (type a) and the "explicit" type chan = a to make it work. Looks ugly, but it compiles.
But if I want to make that function recursive :
let rec recvCmds (type a) (module Ctx : Context with type chan = a) nodeid chan =
Ctx.readMsg chan >>= fun res ->
... more stuff ...
|> OtherModule.getStorageForId (module Ctx)
... more stuff ...
recvCmds (module Ctx) nodeid chan
I pass the module twice, the first time no problem but
I get an error on the recursion line :
The signature for this packaged module couldn't be inferred.
and if I try to specify the signature I get
This expression has type a but an expression was expected of type 'a
The type constructor a would escape its scope
And it seems like I can't use the whole (type chan = a) thing.
If someone could explain what is going on, and ideally a way to work around it, it'd be great.
I could just use a while of course, but I'd rather finally understand these damn modules. Thanks !
The pratical answer is that recursive functions should universally quantify their locally abstract types with let rec f: type a. .... = fun ... .
More precisely, your example can be simplified to
module type T = sig type t end
let rec f (type a) (m: (module T with type t = a)) = f m
which yield the same error as yours:
Error: This expression has type (module T with type t = a)
but an expression was expected of type 'a
The type constructor a would escape its scope
This error can be fixed with an explicit forall quantification: this can be done with
the short-hand notation (for universally quantified locally abstract type):
let rec f: type a. (module T with type t = a) -> 'never = fun m -> f m
The reason behind this behavior is that locally abstract type should not escape
the scope of the function that introduced them. For instance, this code
let ext_store = ref None
let store x = ext_store := Some x
let f (type a) (x:a) = store x
should visibly fail because it tries to store a value of type a, which is a non-sensical type outside of the body of f.
By consequence, values with a locally abstract type can only be used by polymorphic function. For instance, this example
let id x = x
let f (x:a) : a = id x
is fine because id x works for any x.
The problem with a function like
let rec f (type a) (m: (module T with type t = a)) = f m
is then that the type of f is not yet generalized inside its body, because type generalization in ML happens at let definition. The fix is therefore to explicitly tell to the compiler that f is polymorphic in its argument:
let rec f: 'a. (module T with type t = 'a) -> 'never =
fun (type a) (m:(module T with type t = a)) -> f m
Here, 'a. ... is an universal quantification that should read forall 'a. ....
This first line tells to the compiler that the function f is polymorphic in its first argument, whereas the second line explicitly introduces the locally abstract type a to refine the packed module type. Splitting these two declarations is quite verbose, thus the shorthand notation combines both:
let rec f: type a. (module T with type t = a) -> 'never = fun m -> f m

Idris - proving equality of two numbers

I would like to write a function that takes two natural arguments and returns a maybe of a proof of their equality.
I'm trying with
equal : (a: Nat) -> (b: Nat) -> Maybe ((a == b) = True)
equal a b = case (a == b) of
True => Just Refl
False => Nothing
but I get the following error
When checking argument x to constructor Prelude.Maybe.Just:
Type mismatch between
True = True (Type of Refl)
and
Prelude.Nat.Nat implementation of Prelude.Interfaces.Eq, method == a
b =
True (Expected type)
Specifically:
Type mismatch between
True
and
Prelude.Nat.Nat implementation of Prelude.Interfaces.Eq, method == a
b
Which is the correct way to do this?
Moreover, as a bonus question, if I do
equal : (a: Nat) -> (b: Nat) -> Maybe ((a == b) = True)
equal a b = case (a == b) of
True => proof search
False => Nothing
I get
INTERNAL ERROR: Proof done, nothing to run tactic on: Solve
pat {a_504} : Prelude.Nat.Nat. pat {b_505} : Prelude.Nat.Nat. Prelude.Maybe.Nothing (= Prelude.Bool.Bool Prelude.Bool.Bool (Prelude.Interfaces.Prelude.Nat.Nat implementation of Prelude.Interfaces.Eq, method == {a_504} {b_505}) Prelude.Bool.True)
This is probably a bug, or a missing error message.
Please consider reporting at https://github.com/idris-lang/Idris-dev/issues
Is it a known issue or should I report it?
Let's take a look at the implementation of the Eq interface for Nat:
Eq Nat where
Z == Z = True
(S l) == (S r) = l == r
_ == _ = False
You can solve the problem just by following the structure of the (==) function as follows:
total
equal : (a: Nat) -> (b: Nat) -> Maybe ((a == b) = True)
equal Z Z = Just Refl
equal (S l) (S r) = equal l r
equal _ _ = Nothing
You can do it by using with instead of case (dependent pattern matching):
equal : (a: Nat) -> (b: Nat) -> Maybe ((a == b) = True)
equal a b with (a == b)
| True = Just Refl
| False = Nothing
Note that, as Anton points out, this merely a witness on a boolean test result, a weaker claim than proper equality. It might be useful for advancing a proof about if a==b then ..., but it won't allow you to substitute a for b.

How to Compare Types for Equality?

I attempted to compare a String and String, expecting True.
Idris> String == String
Can't find implementation for Eq Type
Then I expected False when comparing a String to a Bool.
Idris> String /= Bool
Can't find implementation for Eq Type
Am I missing an import?
You can't as it would break parametricity, which we have in Idris. We can't pattern match on types. But this would be necessary to write the Eq implementation, for example:
{- Doesn't work!
eqNat : Type -> Bool
eqNat Nat = True
eqNat _ = False -}
Also, if one could pattern match on types, they would be needed in the run-time. Right now types get erased when compiling.
Just to add some simple examples to the above: types can't be pattern matched on, but there's a two parameter type constructor for propositional equality, described in the documentation section on Theorem Proving. Notice that the only constructor, Refl, makes only values of type (=) x x, where both type parameters are the same. (this is ≡ in Agda)
So this will typecheck:
twoPlusTwoEqFour : 2 + 2 = 4
twoPlusTwoEqFour = Refl
so will this:
stringEqString : String = String
stringEqString = Refl
but not this:
stringEqInt : String = Int
stringEqInt = Refl
-- type error: Type mismatch between String and Int
and this needs extra work to prove, because addition is defined by recursion on the left argument, and n + 0 can't be reduced further:
proof : n = n + 0

Propositions vs. boolean values for input validation

I have the following code:
doSomething : (s : String) -> (not (s == "") = True) -> String
doSomething s = ?doSomething
validate : String -> String
validate s = case (not (s == "")) of
False => s
True => doSomething s
After checking the input is not empty I would like to pass it to a function which accepts only validated input (not empty Strings).
As far as I understand the validation is taking place during runtime
but the types are calculated during compile time - thats way it doesn't work. Is there any workaround?
Also while playing with the code I noticed:
:t (("la" == "") == True)
"la" == "" == True : Bool
But
:t (("la" == "") = True)
"la" == "" = True : Type
Why the types are different?
This isn't about runtime vs. compile-time, since you are writing two branches in validate that take care, statically, of both the empty and the non-empty input cases; at runtime you merely choose between the two.
Your problem is Boolean blindness: if you have a value of type Bool, it is just that, a single bit that could have gone either way. This is what == gives you.
= on the other hand is for propositional equality: the only constructor of the type(-as-proposition) a = b is Refl : a = a, so by pattern-matching on a value of type a = b, you learn that a and b are truly equal.
I was able to get your example working by passing the non-equality as a proposition to doSomething:
doSomething : (s : String) -> Not (s = "") -> String
doSomething "" wtf = void $ wtf Refl
doSomething s nonEmpty = ?doSomething
validate : String -> String
validate "" = ""
validate s = doSomething s nonEmpty
where
nonEmpty : Not (s = "")
nonEmpty Refl impossible
As far as I understand the validation is taking place during runtime
but the types are calculated during compile time - thats way it
doesn't work.
That's not correct. It doesn't work because
We need the with form to perform dependent pattern matching, i. e. perform substitution and refinement on the context based on information gained from specific data constructors.
Even if we use with here, not (s == "") isn't anywhere in the context when we do the pattern match, therefore there's nothing to rewrite (in the context), and we can't demonstrate the not (s == "") = True equality later when we'd like to call doSomething.
We can use a wrapper data type here that lets us save a proof that a specific pattern equals the original expression we matched on:
doSomething : (s : String) -> (not (s == "") = True) -> String
doSomething s = ?doSomething
data Inspect : a -> Type where
Match : {A : Type} -> {x : A} -> (y : A) -> x = y -> Inspect x
inspect : {A : Type} -> (x : A) -> Inspect x
inspect x = Match x Refl
validate : String -> String
validate s with (inspect (not (s == "")))
| Match True p = doSomething s p
| Match False p = s