I guess I will never be able to use ViewPatterns
correctly on the first attempt.
I am gradually/grudgingly understanding there are places where it seems you have to use ViewPatterns
. Haskell has too many ways to write pattern matching/condition checking/variable binding; each has annoying restrictions which mean they’re almost, but not quite, equivalent [**]. Let me start with my award for obfuscated Haskell:
parsePacket :: ByteString -> ...
parsePacket (bits 3 -> Just (n, (bits n -> Just (val, bs)))) = ...
From the Wiki page linked from the User Guide. I think that’s equivalent to:
parsePacket _p
| Just (n, _vbs) <- bits 3 _p
, Just (val, bs) <- bits n _vbs = ...
(I’ve introduced a couple of extra vars preceded with _
to hold intermediate values.)
I think that’s not only equivalent in terms of delivered result, but also in performance profile: if bits 3 _p
returns Nothing
, the whole match fails/there’s no attempt to try bits n _vbs
. I see in the guards the arrow <-
points leftwards just like in List and Monad comprehensions; the ‘target’ of the binding is to the left, just as Haskell has always had it in top-level bindings or local: let (Just (n, _vbs)) = ... in ...
.
It’s also comforting to see the function bits
applied to the correct number of arguments I’d expect from its type.
But there are places in the syntax you can’t replace a ViewPattern
call with guards, such as in a lambda-expression:
parsePacket = (\(bits 3 -> Just (n, (bits n -> Just (val, bs)))) -> ...)
But, but! a lambda is exactly the place my eye is expecting a ->
, so now I have to squint at all those parens to figure which one ‘belongs to’ the \
.
I was hoping I could do away with ViewPatterns
and use PatternSynonyms
but:
clunky env (lookup env -> Just val1) (lookup env -> Just val2) = val1 + val2
(clunky
from the User Guide is the standard example/poster-child for an awkward pattern match – see this bit of ancient history 1997.) ViewPatternistas are claiming they’ve overcome the clunkiness. YMMV. I can’t put a PatternSynonym in place of those ViewPattern
calls, because lookup
is using the argument env
as well as the unnamed second or third arguments to clunky
. So this isn’t really a view over a single value.
Also in PatternSynonym
definitions, there are places you have to use ViewPatterns
.
Questions
- Why does the arrow point the wrong way? Yes I see the bindings flow one from another. Guards also express that flow with a comma-list without needing to put the binding backwards. What else in Haskell uses
->
in a comparable way? - Do we really have to hide/make implicit the underlying value? And what if I want a name-binding for that as well as the View?
clunky env (Just val1 <- lookup env v1)@v1 (Just val2 <- lookup env v2)@v2 = val1 + val2
- Could we introduce guards syntax (comma list) in more pattern positions?, for example:
clunky = (\env v1 v2 | Just val1 <- lookup env v1, Just val2 <- lookup env v2 -> val1 + val2)
(Look ma! no parens.) With the same pattern match fail semantics if the lookup
s return Nothing
.
[**] A bit of history
- Wadler on Views 1987; Wadler et al specifically for Haskell 1996.
- SPJ Pattern guards proposal 1997; implemented after Haskell 98; included in H2010 standard.
-
ViewPatterns
GHC 6.10 2008/2009 -
PatternSynonyms
GHC 7.8~8.2 2014~2017
Those extensions are in the ‘same ball park’/draw on Wadler’s 1980’s ideas/have many overlapping use cases, without exactly matching (hah!) capabilities.