Overview
Implicit parameters (enabled with the {-# LANGUAGE ImplicitParams #-}
pragma) provide a way to dynamically bind variables in Haskell.
For example, the following function can be called in any context where ?x
is bound:
Unlike type classes, implicit parameters are bound locally. But what if we want to bind one in the global scope? This would allow a global “default” value, which could then be shadowed locally.
Unfortunately, the following is syntactically invalid:
We turn to the GHC User Manual, only to be further discouraged:
A group of implicit-parameter bindings may occur anywhere a normal group of Haskell bindings can occur, except at top level.
Of course, we won’t let mere syntactic restrictions to get in our way.
Under the hood
Since global binding of implicit parameters is officially not possible,
we need to turn to unofficial methods.
To begin, we pass the -ddump-tc-trace
flag
to GHC and recompile the module containing foo
and bar
.
This makes GHC dump information about what it’s doing during typechecking
the module. There is quite a lot of output, but one line looks interesting:
Good software engineering practice dictates code reuse, and we all know that GHC is a well-engineered piece of software. Therefore, it is not surprising to find that implicit parameters are implemented by piggybacking off of type class resolution with some additional rules to disregard issues like global coherence.
As the above line suggests, implicit parameter resolution is desugared into
the resolution of the GHC.Classes.IP
type class from ghc-prim
.
Even though this module is not documented, we can import it and ask GHCi for more information:
It looks like GHC generates instances of the IP
class on the fly
whenever it sees a binder for an implicit parameter. The name
of the parameter is represented as a type-level symbol. The functional
dependency allows the variable’s type to be resolved just from its name.
Let’s try to write an instance for this class by hand:
GHC happily accepts this definition. Indeed, we can now write
which evaluates to 21
, by picking up the ?x
variable from the
top-level scope. As expected, let ?x = 10 in foo
still evaluates to 10
, as it
shadows the top-level binding.
Barewords
Perhaps this is a good place to stop. But we can go further:
above, we defined only the ?x
variable. It turns out
that we can define an instance for all symbols at once:
This instance brings all possible implicit variables into scope, and assigns their name their value by reflecting the symbol into a string.
Which almost feels like writing Perl!