A large proportion of functions in Base
are defined via a helper function, typically named go
:
-- Data.Set
member :: Ord a => a -> Set a -> Bool
member = go
where
go !_ Tip = False
go x (Bin _ y l r) = case compare x y of
LT -> go x l
GT -> go x r
EQ -> True
-- GHC.Base
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr k z = go
where
go [] = z
go (y:ys) = y `k` go ys
In particular the idiom with that foldr
is the go
calls itself in tight recursion; the original arguments to the outer/public-facing function (k
, z
) remain constant.
In other examples, thereās some pre-calculation from the outer arguments, to hand a local value to the helper:
clunky' env vars@(var1:var2:_)
= help (lookup env var1) (lookup env var2) vars
where
help (Just v1) (Just v2) vars = v1 + v2
help _ _ [var1] = ... some stuff
help _ _ [] = ... some stuff
we do get three equations, one for each right-hand side, ā¦
[page 3 of Pattern Guards and Transformational Patterns, Erwig+SPJ, Haskell Workshop 2000]
(You can tell this is a somewhat made-up example: help
's [var1]
pattern isnāt going to match vars
if vars
has at least two elements per clunky'
's top line.)
The complaint āā¦ it is still clunky. In a big set of equations it becomes hard to remember what each Just
pattern corresponds to. ā¦ā is Iād say a matter of taste. This form is a lot less clunky than the almost perversely clunky form given first in Section 2.2. The form the paper is arguing for/proposing is (for a slightly different signature):
clunky env var1 var2
| Just val1 <- lookup env var1
, Just val2 <- lookup env var2
= val1 + val2
... other equations for clunky
As against the clunky'
form, this has just as many variables and just as many Just
patterns to remember. (These days we might code using ViewPatterns
so var1
, var2
are implicit/not named. We still need to keep track of just as many Just
patterns.)
The proposal went ahead and became part of the H2010 standard. AFAICT, Base
doesnāt use that style of guards much. Is that merely because most of it dates to H98?
To my reading, coding the lookup
s once at the beginning [**] seems cleaner/reduces clutter, and treats the two arguments more symmetrically. Yes thereās a need to introduce a helper/go
; that feels natural as an idiom once youāre used to the idea.
In your code do you prefer those pattern-match guards or a helper? Does go
style seem old-fashioned?
[**] In particular, suppose we want to branch depending whether both lookup
s succeed vs only one does vs neither do. With the H2010 style guards or ViewPatterns
that needs repeating the code for the lookup
s. [āexposes a small deficiencyā, page 4] The optimiser might eliminate common subexpressions, but we still get double/triple the code to read.