Whilst I agree about the type signature the code is still less verbose with implicit parameter, because you don’t have to write the parameter at call site and this can make a massive difference especially when you are refactoring.
Moreover, implicit parameter allows you to give a name (and therefore add semantic) to a parameter inside the type signature : compare
Day -> IO ()
with
(?today :: Day) -> IO ()
The compiler will even check that the name (semantic) matches. If change the signature to ?(yesterday :: Day) -> IO ()
the code won’t compile anymore (which is no the case if I just change the name of an explicit parameter).
I personnaly use implicit param as a semi-global variable, I find it safer than the unsafePerformIO + NOINLINE
trick, mainly because I can change the value locally but also because it makes the use of the global variable explicit.
Keeping that in mind I also don’t override the value unless necessary, nor pluck it out of thin air when necessary. What I mean is, in the way I use IP, there is an implicit semantic to the parameter, all functions using the same Implicit are actually refering to the same.
A good example (I actually use) is ?today
. You might for example generate a report which today’s date.
The header might need today’s date to print the date at which the report has been printed and the body to actually run the report. Let’s say I have a function todayM:: IO Day
the naive implementation would be to use todayM
every time I need it, as in
header = do
today <- todayM
... use it
body = do
today <- todayM
... use
report = header >> body
There are many problem with that one it being what happend if A lauch the report 1millisecond before midnight ? header and body will use a different date.
I could add today
a parameter, so report
becomes
report = do
today <- todayM
header today
body today
However, nothing tells me that I want the same day for both call, when writing body
I’m not sure If should reuse today
or not.
However if use IP, the signatures header :: ?today :: Day => IO ()
and body :: ?today :: Day => IO ()
, tell me something different. Instead of telling me “give me any day” they tell me “if you already have a ?today use it”. The code becomes
report = do
today <- todayM
let ?today = today
header
body