Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are recursive pattern synonyms accepted?

This question (from 5 years ago) asks 'Why are all recursive pattern synonyms rejected?' and its example is still rejected. The User Guide says "Pattern synonyms cannot be defined recursively."

I have a recursive pattern synonym that's accepted (GHC 8.10.2). I can call it, and it loops -- no surprise. So why does it compile?/Is there a plausible use-case for something like this?

Code based on an example in Section 2.3 'Polymorphic pattern synonyms' of the 2016 paper.

data JoinList a = JNil | Unit a | JoinList a `JoinTree` JoinList a    deriving (Eq, Read, Show)

I'm trying to define a pattern synonym Nil. Obviously JNil is a match. But also JNil ``JoinTree`` JNil, and any arbitrary nestings of JoinTree providing all the leafs are JNil.

pattern Nil :: JoinList a
pattern Nil <- JNil
  where Nil = Nil `JoinTree` Nil      -- huh?

wot = Nil `JoinTree` Nil              -- these all compile
wotwot Nil = ()
wotwotwot = wotwot Nil
wotwotwotwot = wotwot wot

Trying to call wot loops, no surprise. Trying to call wotwotwot complains Non-exhaustive patterns in function wotwot.

Full disclosure: what I was playing with/I know this doesn't work [see also below] is:

pattern Nil = JNil                     -- base case matcher, and builder
  where Nil <- Nil `JoinTree` Nil      -- recursion on the matcher, not the builder
                                       -- but can't put `<-` equations after where

The following rejected Recursive pattern synonym definition -- which is what I expect

pattern NilRec <- NilRec `JoinTree` NilRec 
  where NilRec = JNil    

And anyway that wouldn't work: it needs to match NilRec = JNil first as the base case.


To answer @Noughtmare's q "how would you suggest ..." comment to his answer, I'd like to write the decl per above 'what I was playing with' (yes I know that's currently illegal syntax); and get that desugarred to a matcher with two cases, tried sequentially:

case arg of
  { JNil -> ...
  ; Nil `JoinTree` Nil -> ...
  }

Note each of those cases has outermost a data constructor from type JoinList. So the recursive use of Nil is guarded/intermediated, as would be any approach via a ViewPattern. (Which is what the q 5 years ago was asking.) Put it another way: I think the compiler could generate a ViewPattern from the pattern ... = ... where ... <- ....

like image 801
AntC Avatar asked Jul 24 '21 10:07

AntC


1 Answers

Only the matcher cannot be recursive, because that could lead to infinite pattern matching (infinitely large programs). The desugaring of case x of Nil -> y would be:

  case x of
    JNil -> y
    JoinTree x2 x3 -> case x2 of
      JNil -> case x3 of
        JNil -> y
      JoinTree x4 x5 -> case x4 of
        ...

Going on infinitely.

Having a recursive builder is fine, because they can contain arbitrary functions. Here is perhaps a simpler example that shows this without the confusion of constructors:

pattern Foo <- ()
  where Foo = let loop = loop in loop

You can allow arbitrary functions in patterns by using ViewPatterns:

normJoinList (JoinTree (normJoinList -> JNil) (normJoinList -> JNil)) = JNil
normJoinList x = x

pattern Nil <- (normJoinList -> JNil)
  where Nil = JNil

I think that does what you want:

> case Nil `JoinTree` Nil of Nil -> True; _ -> False
True
like image 145
Noughtmare Avatar answered Oct 24 '22 02:10

Noughtmare