introduce lambda-match (explicit match failure and fall-through)
name:
introduce lambda-match (explicit match failure and fall-through)
summary:
Haskell 98 provides *two different translations* of lambda abstractions involving pattern matching, only one of which is directly accessible (Section 3.3 - the other is embedded in the translation of do-notation, see "ok" in Section 3.14).
Providing explicit source notation for the second translation, substantially simplifies programmability of pattern-match fall-through by reification of pattern-match failure, two central language features that are so far only available through the built-in, non-composable case construct.
what:
In Section 3.3, the translation for conventional lambda abstractions with patterns is given as
[| \p1 .. pn-> e |] = \x1 .. xn-> case (x1,..,xn) of
(p1,..,pn) -> e
with xi fresh identifiers, and pattern-match failure resulting in |. the latter is unfortunate, and results in partial functions the coverage of which cannot be combined, but it is forced by translation into conventional lambda calculus.
since computational lambda calculus (\-M) has become a central part of Haskell programming practice, there shall be an alternative form of lambda abstraction, dubbed here lambda-match, with associated translation into \-M:
[| |p1 .. pn-> e |] = \x1 .. xn-> case (x1,..,xn) of
{ (p1,..,pn) -> return e;
_ -> fail "no match" }
[note1: this is the translation in principle - in practice, to enable composition of lambda-matches without further language extensions, a slight modification is needed, wrapping the right-hand sides of the case in a Match constructor, see library, examples, and patch]
a library for composing these lambda-matches provides for
composition of alternative lambda-matches ( (+++)
), match failure
(nomatch
, matchError <message>
) and embedding of the explicit
match monad into pure expressions (splice
, where
\p1..pn->e = splice |p1..pn->e
, also spliceE
-preserving
error messages by using (Either String)
as the match monad, and
allMatches
, using the []
monad to deliver all successful matches).
other lambda-match related auxiliaries include caseOf
as a
user-defined version of case _ of _
, argument supply to matches
( (>|)
), and joining of nested matches (so that, for instance
nest (|<outer>-> (|<inner>-> expr)) = (|<outer> <inner>-> expr)
).
''[note2: an alternative translation would use do-notation instead of case as the target:
[| |p1 .. pn-> e |] = \x1 .. xn-> do {
(p1,..,pn) <- return (x1,..,xn);
return e }
both translations easily accomodate guards and pattern guards as well, the former by building on existing support for these two features in case constructs, the latter without requiring any previous implementation of pattern guards, by lifting (pattern)guards into the match monad:
[| pat <- expr |] = pat <- return expr
[| expr |] = guard expr
]''
implementation impact:
minimal - limited to an extension in parser/desugarer, employing previously inaccessible syntax for lambda-match, and a slight variation of the existing lambda translation; also adds a small library to support composition of matches.
[note3: an updated draft of the composition library and a patch for GHC (about a dozen new lines in the parser, changed from the original proposal email only in requiring parentheses) are provided as attachments to this proposal, together with some examples (updated since original email). please add patches for other Haskell implementations, and test whether they can handle the library code (tried for Hugs and GHC)]
motivation:
- patterns/guards in lambda abstractions are of limited use if match failure cannot be handled
- match failure in lambda abstractions can only lead to an exception
- the only direct way of handling match-failure and fall-through to other alternatives, without something like lambda-match, is not by composing alternatives, but within the built-in construct case; a slightly more indirect way employs the built-in do notation, where again the functionality is not available to (monadic) composition, but only within the do notation
- workarounds to mimick the functionality of lambda-match without syntactic sugar (translating into lambda+do or lambda+case) are so cumbersome and unreadable as to be unusable in practice (translating into lambda+list-comprehension is almost readable, but restricted to lists). compare:
(|(Just x)-> x)
\fresh->do { Just x <- return fresh; return x }
\fresh->case fresh of { Just x -> return x; _ -> fail "no match" }
\fresh->[ x | Just x <- [fresh] ]
- since the functionality is already available in the language, and even explicitly used in the report for a feature as frequently used as the do notation, there should be a language construct making this functionality directly accessible
- the functions named "ok", constructed in Section 3.14 of the report, can be expressed directly using lambda-match (the library contains a function
ok
for this purpose)
do { p <- e; stmts } = e >>= ok (|p-> stmts)
- unlike lambda, lambda-match truly expresses a single alternative in a case statement, as a first-class language expression - it can be named, passed as parameter or result, manipulated as a function, and composed with other alternatives. as a corollary, lambda, case, and other monadic abstractions can be defined easily in terms of lambda-match and its support library
- splice (|x->expr) = \x->expr
- case expr of { pat -> expr; ..} = caseOf expr $ (| pat -> expr ) +++ ..
- ok (|pat->do statements) = \fresh->do { pat <- return fresh; statements }
- while the present proposal suggests lambda-match as a small extension to the language, it also lays the groundwork for a major simplification
- as points 4 and 5 indicate, several core constructs of Haskell need no longer be considered as built into the language, but become user-definable; this would simplify the language definition, implementations, teaching, learning, and tool building
- although the present lambda-match proposal is fairly conservative, it also aims to lay the groundwork for a substantial increase in language expressiveness
- the fundamental idea, used here only to reify pattern-match failure and fall-through, is that of replacing built-in pattern matching with user-definable monadic data parsers and associated combinators
- this same idea lies at the heart of a more radical decomposition of pattern-matching, which has been studied in the literature as first-class patterns (eg, Tullsen, PADL2000)
- having pattern-match alternatives as first-class language constructs also paves the way for user-defined pattern abstractions, or more generally, views (various literature)
this completes the main part of this proposal.
relevant mailing list threads include the main proposal thread and an examples thread demonstrating monadic data parsing and views:
context and references:
as has been pointed out in the thread on replacing and improving pattern guards, Haskell's pattern matching support, even before pattern guards, is monolithic (built-in case is the only way to handle multiple alternative matches) rather than compositional (lambdas seem to represent individual alternatives, but cannot be composed on match fall-through). this implies increased complexity of the language definition, limited expressiveness of its features, and more difficult reasoning, when compared with alternative models (eg, Wolfram Kahl has suggested to adapt his pattern match calculus for Haskell). see, eg.:
http://www.haskell.org/pipermail/haskell-prime/2006-October/001713.html http://www.haskell.org/pipermail/haskell-prime/2006-October/001720.html http://www.haskell.org/pipermail/haskell-prime/2006-October/001723.html http://www.haskell.org/pipermail/haskell-prime/2006-October/001724.html
as well as the PMC home page
http://www.cas.mcmaster.ca/~kahl/PMC/
and a newer thread discussing some differences between this lambda-match proposal and PMC
http://www.haskell.org/pipermail/haskell-prime/2006-October/001823.html
in principle, replacing Haskell's current pattern matching support with a simpler, more compositional model, and defining the current constructs on top of that new core is the way forward, IMHO. in practice, however, I suspect that the committee is less than tempted to introduce such a substantial simplification for Haskell'.
the present proposal is an attempt at a compromise, suggesting a minimal language change to introduce compositional pattern-match failure and fall-through. with lambda-match, it implements only a single language extension (as syntactic sugar), delegating the rest of the functionality to a library. without the sugar, the result of the by-hand translation becomes so hard to read as to be near unusable, while the chosen form of sugaring allows to provide most of the language features discussed in the earlier threads to be provided as a library. this does seem to be a useable balance.
building constructs of the simpler pattern-match model on top of the more complex one is somewhat irksome from a language design perspective, but does at least gain the expressiveness of the simpler model. if programmers make substantial use of this new functionality in Haskell' (as I strongly suspect they will - I have been doing similar translations by hand for some time now), it will be up to Haskell' ' to turn the table, and to define the current complex model on top of a compositional one.
as a preview of this anticipated language refactoring;), it is instructive to compare the two alternative translations of lambda-match sketched above:
- the first directly builds on the existing, complex case constructs with their built-in (pattern) guards and match fall-through support; this eases adding the new, simpler features to implementations that support the old, complex ones (like GHC), but completely foregoes any attempt to simplify those implementations in the process; [the attached patch for GHC follows this route]
- the second translation avoids any direct reference to case, employing do-notation instead; this requires slightly more effort, eg. in translating pattern guards into the match monad, but is eminently suitable for adding the new features to implementations that do not support pattern guards yet, or that simply want to reduce the number of built-in constructs. [patchers for Hugs, etc., might want to follow this route]
finally, while it is not directly relevant for the present proposal, Tullsen's PADL 2000 paper investigates one way in which monadic data parsing can be built on to go beyond first-class match alternatives all the way to first-class patterns:
http://citeseer.ist.psu.edu/tullsen00first.html
this, in turn, relates directly to Views and PatternSynonyms.