Syntax for lazy pattern matching with `as` syntax

Viewed 263

In (vanilla) GHCi 8.6.5 following function was prefectly valid:

f xs@ ~(x:xt) = xs

If I now do the same in 9.0.1 I get an error

Suffix occurrence of @. For an as-pattern, remove the leading whitespace.

Just removing the white space between @ and ~ doesn't seem to suffice, as then @~ would be interpreted as an operator, so the only valid variation I found was

f xs@(~(x:xt)) = xs

I'd like to know the following, for which I couldn't find answers in the change notes:

  1. What exactly changed from 8.6.5 to 9.0.1 that introduced this incompatibility?
  2. Is xs@(~(x:xt)) really the best way to write this pattern, or is there a preferred way over this?
2 Answers

The changes to the handling of ~ and @ in GHC 9.0 are described here. Quoting from the migration guide:

GHC 9.0 implements Proposal 229, which means that the !, ~, and @ characters are more sensitive to preceding and trailing whitespace than they were before. As a result, some things which used to parse one way will now parse differently (or throw a parse error).

Adding parentheses (variable@(~pattern)) is a good solution. Alternatively, you could use a let or where binding, or a separate lazy case:

  1. rehead :: a -> [a] -> [a]
    rehead x' xs0 = x' : xs
      where
        _x : xs = xs0
    
  2. rehead :: a -> [a] -> [a]
    rehead x' xs0 = let
     _x : xs = xs0
     in x' : xs
    
  3. {-# Language PatternGuards #-}
    
    rehead :: a -> [a] -> [a]
    rehead x' xs0
      | let _x : xs = xs0
      = x' : xs
    

    This can be very helpful if you want to use these bindings in subsequent guards.

  4. rehead :: a -> [a] -> [a]
    rehead x' xs0 = case xs0 of
      ~(_x : xs) -> x' : xs
    

All of these options are maximally lazy:

  • head (rehead 5 [1, 2])
  • = head (rehead 5 [])
  • = head (rehead 5 undefined)
  • = 5

If you’re using {-# Language Strict #-}, then you must write the let/where bindings as ~(_x : xs) = xs0 to allow [], and the list parameter binding as ~xs0 to allow undefined; to get an irrefutable pattern (not just lazy) with case, you must write ~(~(_x : xs)).

Related