
Browsing some docos for a completely other purpose, I came across this code:
f' [x, y] | True <- x, True <- y = True f' _ = False
(In User Guide 6.7.4.5 Matching of Pattern Synonyms.) That business with the comma and left-arrows? They're 'Pattern guards', Language Report 2010 section 3.13. That also specs 'local bindings' introduced by `let`. In 10 years of reading Haskell code, I've never seen them. Does anybody use them? Are they more ergonomic than guards as plain Boolean expressions? Are 'local bindings' any different vs shunting the `let` to the rhs of the `=`? I'd write that code as:
f'' [x@True, y@True] = True f'' _ = False
I can see the rhs of the matching arrow could in general be a more complex expression. But to express that you could put a more complex Boolean guard(?) AntC

On Fri, Oct 01, 2021 at 04:06:45PM +1300, Anthony Clayden wrote:
Browsing some docos for a completely other purpose, I came across this code:
f' [x, y] | True <- x, True <- y = True f' _ = False
(In User Guide 6.7.4.5 Matching of Pattern Synonyms.)
A fairly synthetic exmaple...
In 10 years of reading Haskell code, I've never seen them. Does anybody use them? Are they more ergonomic than guards as plain Boolean expressions? Are 'local bindings' any different vs shunting the `let` to the rhs of the `=`?
The pattern guards in the example are far from compelling as written, but I do regularly use them in I hope more natural contexts.
I'd write that code as:
f'' [x@True, y@True] = True f'' _ = False
Or with no guards at all: f'' [x, y] = x && y f'' _ = False More realistic examples: https://github.com/kazu-yamamoto/dns/blob/master/internal/Network/DNS/Decode... Or code to process a possibly not yet complete (to be continued) SMTP greeting: smtpGreeting :: Int -> SmtpReply -> SmtpM B.ByteString smtpGreeting _ r | replyCont r = pure B.empty | code <- replyCode r , code `div` 100 /= 2 = B.empty <$ modify' bail code | otherwise = smtpSendHello where bail code s = s { smtpErr = ProtoErr code $ replyText r } which would otherwise be something like: smtpGreeting :: Int -> SmtpReply -> SmtpM B.ByteString smtpGreeting _ r = if replyCont r then pure B.empty else let code = replyCode r in if code `div` 100 == 2 then smtpSendHello else B.empty <$ modify' bail code where bail code s = s { smtpErr = ProtoErr code $ replyText r } but I find the pattern guard form to read "declarative", with less "if then else" baggage and nesting getting in the way of seeing the essential conditions. -- Viktor.

On 01/10/2021 07:29, Viktor Dukhovni wrote:
More realistic examples:
https://github.com/kazu-yamamoto/dns/blob/master/internal/Network/DNS/Decode...
Or code to process a possibly not yet complete (to be continued) SMTP greeting:
smtpGreeting :: Int -> SmtpReply -> SmtpM B.ByteString smtpGreeting _ r | replyCont r = pure B.empty | code <- replyCode r , code `div` 100 /= 2 = B.empty <$ modify' bail code | otherwise = smtpSendHello where bail code s = s { smtpErr = ProtoErr code $ replyText r }
[...]
-- Viktor.
Perhaps redundantly, this particular instance of a pattern guard can, in my opinion, be written slightly less suggestively with a 'let' pattern guard (not sure if that's the right name): [...] | let code = replyCode r , code `div` 100 /= 2 = B.empty <$ modify' bail code [...] Non-'let' pattern guards, in my experience, are most useful in two cases: 1. You want to do some additional pattern matching on a non-boolean value after having arrived in an interesting case in a larger pattern-match. For example, in the request URL parsing code in my pastebin-haskell: (Some cases and additional code elided) parseRequest :: Method -> ByteString -> Maybe WhatRequest parseRequest method path = let comps = BS.split (fromIntegral (ord '/')) (trimSlashes path) in case (method, comps) of (GET, []) -> Just GetIndex (GET, [x]) | canBeKey x -> Just (ReadPaste x) -- [...] (GET, ["paste", x]) | canBeKey x -> Just (ReadPasteOld x) (GET, ["highlight.pack.css"]) -> Just HighlightCSS (GET, [x]) | Just (path', mime) <- List.lookup x staticFiles -> Just (StaticFile mime path') (POST, ["paste"]) -> Just StorePaste _ -> Nothing Original code here: https://github.com/tomsmeding/pastebin-haskell/blob/25de4aa531a782ca8c34b409... 2. Actually a special case of (1.): this is particularly useful with GADTs and "discovered" type evidence, like in https://github.com/AccelerateHS/accelerate/blob/1ab75f1eb01a1b427563808057d4... . In general, these pattern guards seem to me the Haskell version of Agda's with-patterns [1], which also allow pattern matching on an _additional_ value after the first pattern match has already found a branch. - Tom [1]: https://agda.readthedocs.io/en/v2.5.2/language/with-abstraction.html

Am 01.10.21 um 09:04 schrieb Tom Smeding:
2. Actually a special case of (1.): this is particularly useful with GADTs and "discovered" type evidence, like in https://github.com/AccelerateHS/accelerate/blob/1ab75f1eb01a1b427563808057d4... .
Yes, exactly. It is not allowed to do that in let-bindings (including where-clauses), only in case matches, pattern guards, and monadic binds. Cheers Ben -- I would rather have questions that cannot be answered, than answers that cannot be questioned. -- Richard Feynman

Anthony Clayden
Browsing some docos for a completely other purpose, I came across this code:
f' [x, y] | True <- x, True <- y = True f' _ = False
(In User Guide 6.7.4.5 Matching of Pattern Synonyms.)
That business with the comma and left-arrows? They're 'Pattern guards', Language Report 2010 section 3.13. That also specs 'local bindings' introduced by `let`.
In 10 years of reading Haskell code, I've never seen them. Does anybody use them? Are they more ergonomic than guards as plain Boolean expressions? Are 'local bindings' any different vs shunting the `let` to the rhs of the `=`?
I'd write that code as:
f'' [x@True, y@True] = True f'' _ = False
I can see the rhs of the matching arrow could in general be a more complex expression. But to express that you could put a more complex Boolean guard(?)
I've used it occasionally, e.g when dealing with exceptions. I think this is the most recent example: ``` lastExceptionHandler :: LoggerSet -> SomeException -> IO () lastExceptionHandler logger e | Just TimeoutThread <- fromException e = return () | otherwise = do logFatalIoS logger $ pack $ "(ws) uncaught exception: " <> displayException e flushLogStr logger ``` (That's the exception handler I installed with `setUncaughtExceptionHandler` in a web service to deal with https://github.com/yesodweb/wai/issues/852) /M -- Magnus Therning OpenPGP: 0x927912051716CE39 email: magnus@therning.org @magthe@mastodon.technology http://magnus.therning.org/ Action is the foundational key to all success. — Pablo Picasso
participants (5)
-
Anthony Clayden
-
Ben Franksen
-
Magnus Therning
-
Tom Smeding
-
Viktor Dukhovni