Opened 7 years ago

Closed 7 years ago

Last modified 3 years ago

#4061 closed feature request (wontfix)

Implement mutually recursive view patterns

Reported by: batterseapower Owned by:
Priority: normal Milestone:
Component: Compiler Version: 6.13
Keywords: Cc:
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:


I got bit by the fact that mutually recursive view patterns don't work when writing code a bit like this:

{-# LANGUAGE ViewPatterns #-}
module RecursiveViewPatterns where

-- Doesn't work: "Not in scope: 'foo'"
(foo, unzip -> (bars, bazs)) = undefined
(spqr, ($ foo) -> metavariable) = undefined

-- Works fine:
-- (foo, unzip -> (bars, bazs)) = undefined
-- (spqr, mk_metavariable) = undefined
-- metavariable = mk_metavariable foo

(I've started using view patterns very aggressively to eliminate almost every linearly-used binding in my programs!)

I note that the GHC users guide ( mentions that this feature is not yet implemented, but the restriction may be lifted in future. Consider this ticket a vote for that change.

Change History (3)

comment:1 Changed 7 years ago by simonpj

Thinking about it some more, I'm a bit dubious. You are proposing:

  • In a letrec, bring all the bound variables into scope before checking the patterns

But consider

(f -> Just x, Just f) = e

This is disallowed at the moment (by the left-to-right rule), but your rule would allow it. But the dynamic semantics of pattern matching is supposed to be left-to-right, and we literally can't match the first view pattern until we've matched the (Just f) pattern. So I don't think we want 'f' to be in scope in the whole pattern.

If instead we'd had

( ~(f -> Just x), Just f ) = e

then the dynamic semantics would be ok, because the first (lazy) pattern always matches, which allows us to get to the second and thereby bind f. But I don't think we want lexical scope to depend on lazy patterns!

Now suppose we had

(f -> Just x) = e1
Just f = e2

You'd allow that too, and more reasonably, because the patterns are evaluated on demand. But it's hard to think of a rule that makes sense here. We can hardly say "the variables bound by p=e are in scope in 'e', and throughout all definitions in the group, but not in 'p' itself".

I suppose a compromise might be to extend the left-to-right rule to give a top-to-bottom scoping:

  • In a binding p=e, the pattern p may mention variables bound in syntactically earlier bindings in the same binding group (as well as variables bound earlier still). Or, to put it another way, the variables bound by a definition scope over the patterns of syntactically later pattern bindings in the same let/where group.

Then we'd have

-- Allowed
Just f = ...x...
(f -> Just x) = e1

-- Not allowed
(f -> Just x) = e1
Just f = ...x...

It seems a little ad hoc, and I'm unsure whether it's worth it.

I suppose it would be more in the order-independent spirit of Haskell to say that the compiler should try to sort the definitions into an order that allows the above rule to work, so both of the above would be allowed, but somthing mutually recursive would not, such as

-- Disallowed
(g, f -> Just x) = e1
(f, g -> Just y) = e2

But that seems wildly over the top.

Laking an obvious design choice, I'm disinclined to make a change.


comment:2 Changed 7 years ago by igloo

Resolution: wontfix
Status: newclosed

I'm closing this ticket as we don't see a way to make progress on it.

comment:3 Changed 3 years ago by rwbarton

difficulty: Unknown

BTW, since the view patterns in your example are not mutually recursive, you can use this hack to break up the top level scope:

{-# LANGUAGE ViewPatterns, TemplateHaskell #-}
module RecursiveViewPatterns where

(foo, unzip -> (bars, bazs)) = undefined
[d||]    -- or  return []
(spqr, ($ foo) -> metavariable) = undefined
Note: See TracTickets for help on using tickets.