Description
While working on defining Kan extensions and whatnot, some of the error messages are pretty hard to decipher unless the --explain
flag is used. Compare
Without `--explain`
$ dhall <<< ./Coyoneda/lower
Use "dhall --explain" for detailed errors
↳ ./Coyoneda/lower
Error: Expression doesn't match annotation
∀(… : ∀(… : …)
→ …)
→ ∀(… : - Type
+ { … : … }
)
→ ∀(… : - ∀(… : …) → …
+ Type
)
→ - … …
+ ∀(… : …) → …
let lower
: ∀(f : Type → Type) → ∀(a : Type) → Coyoneda f a → f a
= λ(f : Type → Type)
→ λ(functor : Functor f)
→ λ(a : Type)
→ λ(coyoneda : Coyoneda f a)
→ coyoneda
(f a)
(λ(b : Type) → λ(k : b → a) → λ(x : f b) → functor.map b a k x)
in lower
With `--explain`
$ dhall --explain <<< ./Coyoneda/lower
↳ ./Coyoneda/lower
Error: Expression doesn't match annotation
∀(… : ∀(… : …)
→ …)
→ ∀(… : - Type
+ { … : … }
)
→ ∀(… : - ∀(… : …) → …
+ Type
)
→ - … …
+ ∀(… : …) → …
Explanation: You can annotate an expression with its type or kind using the
❰:❱ symbol, like this:
┌───────┐
│ x : t │ ❰x❱ is an expression and ❰t❱ is the annotated type or kind of ❰x❱
└───────┘
The type checker verifies that the expression's type or kind matches the
provided annotation
For example, all of the following are valid annotations that the type checker
accepts:
┌─────────────┐
│ 1 : Natural │ ❰1❱ is an expression that has type ❰Natural❱, so the type
└─────────────┘ checker accepts the annotation
┌───────────────────────┐
│ Natural/even 2 : Bool │ ❰Natural/even 2❱ has type ❰Bool❱, so the type
└───────────────────────┘ checker accepts the annotation
┌────────────────────┐
│ List : Type → Type │ ❰List❱ is an expression that has kind ❰Type → Type❱,
└────────────────────┘ so the type checker accepts the annotation
┌──────────────────┐
│ List Text : Type │ ❰List Text❱ is an expression that has kind ❰Type❱, so
└──────────────────┘ the type checker accepts the annotation
However, the following annotations are not valid and the type checker will
reject them:
┌──────────┐
│ 1 : Text │ The type checker rejects this because ❰1❱ does not have type
└──────────┘ ❰Text❱
┌─────────────┐
│ List : Type │ ❰List❱ does not have kind ❰Type❱
└─────────────┘
Some common reasons why you might get this error:
● The Haskell Dhall interpreter implicitly inserts a top-level annotation
matching the expected type
For example, if you run the following Haskell code:
┌───────────────────────────────┐
│ >>> input auto "1" :: IO Text │
└───────────────────────────────┘
... then the interpreter will actually type check the following annotated
expression:
┌──────────┐
│ 1 : Text │
└──────────┘
... and then type-checking will fail
────────────────────────────────────────────────────────────────────────────────
You or the interpreter annotated this expression:
↳ λ(f : Type → Type)
→ λ ( functor
: ( λ(f : Type → Type)
→ { map :
∀(a : Type) → ∀(b : Type) → ∀(g : a → b) → ∀(fa : f a) → f b
}
)
f
)
→ λ(a : Type)
→ λ ( coyoneda
: ( λ(g : Type → Type)
→ λ(a : Type)
→ ∀(r : Type) → (∀(b : Type) → (b → a) → g b → r) → r
)
f
a
)
→ coyoneda
(f a)
(λ(b : Type) → λ(k : b → a) → λ(x : f b) → functor.map b a k x)
... with this type or kind:
↳ ∀(f : Type → Type)
→ ∀(a : Type)
→ (∀(r : Type) → (∀(b : Type) → (b → a) → f b → r) → r)
→ f a
... but the inferred type or kind of the expression is actually:
↳ ∀(f : Type → Type)
→ ∀ ( functor
: { map : ∀(a : Type) → ∀(b : Type) → ∀(g : a → b) → ∀(fa : f a) → f b }
)
→ ∀(a : Type)
→ ∀(coyoneda : ∀(r : Type) → (∀(b : Type) → (b → a) → f b → r) → r)
→ f a
────────────────────────────────────────────────────────────────────────────────
let lower
: ∀(f : Type → Type) → ∀(a : Type) → Coyoneda f a → f a
= λ(f : Type → Type)
→ λ(functor : Functor f)
→ λ(a : Type)
→ λ(coyoneda : Coyoneda f a)
→ coyoneda
(f a)
(λ(b : Type) → λ(k : b → a) → λ(x : f b) → functor.map b a k x)
in lower
The problem is that the type given in the let binding is missing the Functor f
, but it's really hard to discern that from the diff format used for mismatches. in particular, this bit really helped to understand the problem:
Expanded names
You or the interpreter annotated this expression:
↳ λ(f : Type → Type)
→ λ ( functor
: ( λ(f : Type → Type)
→ { map :
∀(a : Type) → ∀(b : Type) → ∀(g : a → b) → ∀(fa : f a) → f b
}
)
f
)
→ λ(a : Type)
→ λ ( coyoneda
: ( λ(g : Type → Type)
→ λ(a : Type)
→ ∀(r : Type) → (∀(b : Type) → (b → a) → g b → r) → r
)
f
a
)
→ coyoneda
(f a)
(λ(b : Type) → λ(k : b → a) → λ(x : f b) → functor.map b a k x)
... with this type or kind:
↳ ∀(f : Type → Type)
→ ∀(a : Type)
→ (∀(r : Type) → (∀(b : Type) → (b → a) → f b → r) → r)
→ f a
... but the inferred type or kind of the expression is actually:
↳ ∀(f : Type → Type)
→ ∀ ( functor
: { map : ∀(a : Type) → ∀(b : Type) → ∀(g : a → b) → ∀(fa : f a) → f b }
)
→ ∀(a : Type)
→ ∀(coyoneda : ∀(r : Type) → (∀(b : Type) → (b → a) → f b → r) → r)
→ f a
Seeing the full names allowed my brain to spot the difference that was harder to see with the truncated version.
Can we add this bit to the normal error messages? The diff is nice for "trivial" errors like Integer
vs. Natural
, or one level of record name mismatches. For anything "non-trivial," it's pretty hard to figure out the problem.
If not, is there another approach to having more understandable error messages?