-
Notifications
You must be signed in to change notification settings - Fork 28
/
2014-12-11-implicit-params.lhs
110 lines (89 loc) · 4.25 KB
/
2014-12-11-implicit-params.lhs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
---
title: "24 Days of GHC Extensions: Implicit Parameters"
---
> {-# LANGUAGE ImplicitParams #-}
> import Data.Char
[Yesterday](/posts/2014-12-10-nullary-type-classes.html) we looked at nullary
type classes - type classes that don't vary over any types - and saw how they
can be used to leave part of a program undefined. Our particular example looked
at building a library that needs to call a logging function, but we leave the
implementation up to the library author.
However, using a nullary type class for this has some drawbacks - the biggest is
that we are now tied to a single choice of logging function. This can be
problematic if we ever need to vary what it means to log throughout the life
time of a program. This may crop up if we decide that we need to transform or
discard log entries at smaller parts of our program.
To solve this, lets rewrite our library to take logging as a parameter to a
function:
> type LogFunction = String -> IO ()
>
> type Present = String
>
> queueNewChristmasPresents :: LogFunction -> [Present] -> IO ()
> queueNewChristmasPresents logMessage presents = do
> mapM (logMessage . ("Queueing present for delivery: " ++)) presents
> return ()
This isn't much different from what we saw yesterday, but it quickly becomes
painful to use in practice. Whenever we want to abstract over
`queueNewChristmasPresents`, we have to either choose to commit to a specific
log function, or we have to manually propagate the `LogFunction` to the parent
function.
One solution might be to move the `LogFunction` to a reader monad, but this
still carries a cost and the program will need to be transformed. A less
invasive technique is to use an *implicit* parameter.
Implicit parameters act like parameters to a function, except the caller never
has to apply the function to the argument. Instead, the argument is
automatically passed to the function by merely being in scope. Let's see how
this works out for our logger:
> queueNewChristmasPresents2 :: (?logMessage :: LogFunction) => [Present] -> IO ()
> queueNewChristmasPresents2 presents = do
> mapM (?logMessage . ("Queueing present for delivery: " ++)) presents
> return ()
As you can see, something interesting has happened. The `LogFunction` is no
longer a parameter to the function, but is rather part of the *context* of the
function - constraints that must be satisfied when we use the program. The body
of the program is mostly the same, other than the leading `?` that prefixes
implicit parameters.
To supply an implicit parameter, all we need to do is bring an appropriately
named variable into scope:
> ex1 :: IO ()
> ex1 =
> let ?logMessage = \t -> putStrLn ("[XMAS LOG]: " ++ t)
> in queueNewChristmasPresents2 ["Cuddly Lambda", "Gamma Christmas Pudding"]
```
.> ex1
[XMAS LOG]: Queueing present for delivery: Cuddly Lambda
[XMAS LOG]: Queueing present for delivery: Gamma Christmas Pudding
```
Perfect, we've got the same type of programs as we saw yesterday!
However, we now have the ability to use two different types of loggers in the
same program:
> ex2 :: IO ()
> ex2 = do
> -- Specifies its own logger
> ex1
>
> -- We can locally define a new logging function
> let ?logMessage = \t -> putStrLn (zipWith (\i c -> if even i
> then c
> else toUpper c)
> [0..]
> t)
> queueNewChristmasPresents2 ["Category Theory Books"]
```
[XMAS LOG]: Queueing present for delivery: Cuddly Lambda
[XMAS LOG]: Queueing present for delivery: Gamma Christmas Pudding
QUeUeInG PrEsEnT FoR DeLiVeRy: CaTeGoRy THeOrY BoOkS
```
Neat!
Implicit parameters are something of a love-hate extension, to the best of my
knowledge - though it can be mighty useful sometimes. Also, as with many aspects
of Haskell - it is useful for things that were never even anticipated. Tom Ellis
has a blog post that demonstrates how one can ["fake" a module
system](http://h2.jaguarpaw.co.uk/posts/modules-for-lennart/) by using implicit
parameters.
----
*This post is part of
[24 Days of GHC Extensions](/pages/2014-12-01-24-days-of-ghc-extensions.html) -
for more posts like this, check out the
[calendar](/pages/2014-12-01-24-days-of-ghc-extensions.html)*.