One thing I do not fully understand about Haskell is declaring functions and their types: is it something you have to do or is it just something you should do for clarity? Or are there certain scenarios where you need to do it, just not all?
You don’t need to declare the type of any function that uses only standard Haskell type system features. Haskell 98 is specified with global type inference, meaning that the types of all top-level bindings are guaranteed to be inferable.
However, it’s good practice to include type annotations for top-level definitions, for a few reasons:
Verifying that the inferred type matches your expectations
Helping the compiler producing better diagnostic messages when there are type mismatches
Most importantly, documenting your intent and making the code more readable for humans!
As for definitions in where
clauses, it’s a matter of style. The conventional style is to omit them, partly because in some cases, their types could not be written explicitly before the ScopedTypeVariables
extension. I consider the omission of scoped type variables a bit of a bug in the 1998 and 2010 standards, and GHC is the de facto standard compiler today, but it’s still a nonstandard extension. Regardless, it’s good practice to include annotations where possible for nontrivial code, and helpful for you as a programmer.
In practice, it’s common to use some language extensions that complicate type inference or make it “undecidable”, meaning that, at least for arbitrary programs, it’s impossible to always infer a type, or at least a unique “best” type. But for the sake of usability, extensions are usually designed very carefully to only require annotations at the point where you actually use them.
For example, GHC (and standard Haskell) will only infer polymorphic types with top-level forall
s, which are normally left entirely implicit. (They can be written explicitly using ExplicitForAll
.) If you need to pass a polymorphic function as an argument to another function like (forall t. …) -> …
using RankNTypes
, this requires an annotation to override the compiler’s assumption that you meant something like forall t. (… -> …)
, or that you mistakenly applied the function on different types.
If an extension requires annotations, the rules for when and where you must include them are typically documented in places like the GHC User’s Guide, and formally specified in the papers specifying the feature.