6
votes

I'm building a module where every time I write a function, it calls a dozen other functions that don't exist yet. Obviously they'll exist eventually, but it would be nice to be able to do a syntax check before I've finished writing the code.

Is there some combination of flags I can use to make GHC emit warnings rather than errors for "name foo is not in scope"?

(It would be nice actually if GHC could just pick a type signature for the non-existent names, and confirm that it's still possible for the program to type-check. This is nearly what the "type holes" feature does — but to use that, you still have to manually define all the identifiers.)

2
I daresay it's good practise to just write out the signature and an = undefined for such functions... but, good question, I've also sometimes wanted that for ideas so weird I'm not sure it's worth trying to get them to type-check, since it might not even parse. - leftaroundabout
Another way just to check if it is correct syntax is to use something like pointfree and check for "Parse error: EOF", but this makes it difficult to do anything more than a single-line expression. - bheklilr
@leftaroundabout In my case, I'm building a parser. That means there's hundreds of non-existent identifiers, all of which should be parsers. When I eventually finish writing all the pieces, it should work. But it would be nice to catch syntax errors before I get to the end... - MathematicalOrchid
I'll secon @leftaroundabout's type signature+undefined approach. I use that all the time. If you want, you can even skip the type signature, leaving the variables fully polymorphic, but the more type signatures you can add, the better your type error messages are likely to be. - dfeuer
@leftaroundabout I am assuming OP really meant "syntax and type check". If you really want just a syntax check, you don't need to define everything - getting out of scope errors means the module had to parse correctly first. - Ørjan Johansen

2 Answers

11
votes

Use named TypedHoles:

> let f x = _g . _h x $ x
    Found hole ‘_g’ with type: b0 -> c
    Where: ‘b0’ is an ambiguous type variable
           ‘c’ is a rigid type variable bound by
               the inferred type of f :: s -> c at <interactive>:2:5
    Relevant bindings include
      x :: s (bound at <interactive>:2:7)
      f :: s -> c (bound at <interactive>:2:5)
    In the first argument of ‘(.)’, namely ‘_g’
    In the expression: _g . _h x
    In the expression: _g . _h x $ x

    Found hole ‘_h’ with type: s -> s -> b0
    Where: ‘b0’ is an ambiguous type variable
           ‘s’ is a rigid type variable bound by
               the inferred type of f :: s -> c at <interactive>:2:5
    Relevant bindings include
      x :: s (bound at <interactive>:2:7)
      f :: s -> c (bound at <interactive>:2:5)
    In the expression: _h
    In the second argument of ‘(.)’, namely ‘_h x’
    In the expression: _g . _h x

So this gives you that _g :: b0 -> c and _h :: s -> s -> b0 with the context of x :: s and f :: s -> c. The type-checker can infer these types most of the time (this is the point of TypedHoles), and you can give them names. If you want, you can define all your functions with a _ as the first character of the symbol name, then later use your editor to replace _(.+)\b with \1. If you want to work around the lens convention of using _name for a record field then just put 2 underscores on your hole name.

This will still keep your code from compiling though, but if you use this in combination with -fdefer-type-errors they'll be reported as warnings instead, allowing your type errors to occur at runtime.

3
votes

My usual approach is to define missing functions or values as undefined. It's easy to define and leaves you with a convenient marker that the function in question isn't defined yet.

I'm aware that this doesn't answer the OP's question, since the functions still have to be defined by hand, but I thought it could be useful anyway.