Skip to content

Provide non-truncated version in normal error messages #532

Open
@joneshf

Description

@joneshf

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions