-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathapplicative-comprehensions.trac
93 lines (67 loc) · 4.07 KB
/
applicative-comprehensions.trac
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
Applicative Comprehensions
This is a proposal to add support to GHC for desugaring certain instances of comprehensions into Applicative expression where possible. This feature is related to both ApplicativeDo and MonadComprehensions.
== Summary
The {{{ApplicativeComprehensions}}} language extension would cause GHC to attempt to desugar comprehensions using a similar approach to {{{MonadComprehensions}}}, but in a way that only results in code with an {{{Applicative}}} constraint.
Any comprehension that includes only generator expressions where the bindings defined by a generator expression are not used in any subsequent generator expression can be desugared using only operations from the {{{Applicative}}} typeclass, so an expression like
{{{
[ expr_0 | pat_1 <- expr_1
, pat_2 <- expr_2
, ...
, pat_n <- expr_n
]
}}}
can be desugared to
{{{
(\ pat_1 pat_2 ... pat_n ->
pure expr_0) <$> expr_1
<*> expr_2
<*> ...
<*> expr_n
}}}
== Motivation
As explained in the ''Motivation'' section for ApplicativeDo, some kinds of {{{Applicative}}} code can be obscure or difficult to read. The example given there is
{{{
(\x y z -> x*y + y*z + z*x) <$> expr1 <*> expr2 <*> expr3
}}}
This code, when translated into an equivalent {{{Applicative}}} comprehension, becomes
{{{
[ x*y + y*z + z*x | x <- expr1, y <- expr2, z <- expr3 ]
}}}
which has fewer operators and clearer structure, but still bears a strong resemblance to the desugared code, especially when compared with the equivalent {{{ApplicativeDo}}} expression.
In addition to being terse and clear in their own right, {{{ApplicativeComprehensions}}} have some small advantages over the {{{ApplicativeDo}}} extension: the {{{ApplicativeDo}}} transformation as implemented in GHC 8 is an exclusively syntactic translation, which can lead to some unexpected corner cases. For example, the following example code typechecks in GHC 8 with {{{LANGUAGE ApplicativeDo}}}:
{{{
-- this typechecks with ApplicativeDo
incrA :: Applicative f => f Int -> f Int
incrA nA = do
n <- nA
return (n + 1)
}}}
However, the eta-expanded version of the same code _does not_ typecheck, because the {{{ApplicativeDo}}} transformation only happens if the {{{do}}}-notation block ends with a literal invocation of {{{return}}} or {{{pure}}}:
{{{
-- this does NOT typecheck with ApplicativeDo
incrA' :: Applicative f => f Int -> f Int
incrA' nA = do
n <- nA
(\ x -> return x) (n + 1)
}}}
The corresponding problem is impossible to express with {{{ApplicativeComprehensions}}}, because a call to {{{pure}}} or {{{return}}} is ''always'' implicitly applied to the return value of the comprehension. The comprehension-based equivalent to {{{incrA}}} is:
{{{
incrA'' :: Applicative f => f Int -> f Int
incrA'' nA = [ n + 1 | n <- nA ]
}}}
== Some Standing Questions
It's possible to desugar {{{ApplicativeComprehensions}}}-compatible expressions that contain guards into expressions with an `Alternative` constraint, e.g. by translating
{{{
[ expr_0 | pat_1 <- expr_1
, pat_2 <- expr_2
, guard_expr
]
}}}
into
{{{
(\ pat_1 pat_2 () -> expr_0) <$> expr_1
<*> expr_2
<*> guard guard_expr
}}}
but this would be subject to the same restrictions as before: the guard expression would not be able to reference the variables from any of the other pattern bindings. This doesn't seem as useful as {{{ApplicativeComprehensions}}} in general, but it might be worthwhile to include.
If a given instance of {{{do}}}-notation doesn't satisfy the criteria for {{{ApplicativeDo}}}, then it implicitly is given a {{{Monad}}} constraint instead of an {{{Applicative}}} constraint. Fallback for {{{ApplicativeComprehensions}}} is not as clear, in part because it might be determined by whether a given source file also has {{{MonadComprehensions}}} enabled or not, or whether {{{MonadComprehensions}}} implies {{{ApplicativeComprehensions}}} or vice versa.