Opened 4 years ago

Closed 21 months ago

Last modified 20 months ago

#4359 closed feature request (fixed)

Implement lambda-case/lambda-if

Reported by: batterseapower Owned by: simonmar
Priority: high Milestone: 7.6.1
Component: Compiler Version: 7.1
Keywords: Cc: hydo@…, cmoore@…, hackage.haskell.org@…, uzytkownik2@…, leather@…, illissius@…, mikhail.vorozhtsov@…, anton.nik@…, dterei, g9ks157k@…
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Difficulty: Unknown
Test Case: Blocked By:
Blocking: Related Tickets:

Description (last modified by simonpj)

I put together a patch for this Haskell' proposal (http://hackage.haskell.org/trac/haskell-prime/ticket/41)

    Prelude> (if then "Haskell" else "Cafe") False
    "Cafe"
    Prelude> (case of 1 -> "One"; _ -> "Not-one") 1
    "One"

There seems to be some support for integrating this proposal into GHC (see http://permalink.gmane.org/gmane.comp.lang.haskell.cafe/81366)

The attached patches implement the feature, test it and document it.

See also the wiki page LambdasVsPatternMatching.

Attachments (10)

LambdaCase-Compiler.patch (181.2 KB) - added by batterseapower 4 years ago.
LambdaCase-Testsuite.patch (92.9 KB) - added by batterseapower 4 years ago.
multi-clause-lambdas.patch (17.3 KB) - added by mikhail.vorozhtsov 22 months ago.
Multi-clause lambda abstractions extension, v4, another rebase
multi-clause-lambdas-testsuite.patch (7.9 KB) - added by mikhail.vorozhtsov 22 months ago.
Tests for the MultiClauseLambdas extension, v2, another rebase
one-arg-lambda-case.patch (20.9 KB) - added by mikhail.vorozhtsov 21 months ago.
\case-expressions, patch for the GHC
one-arg-lambda-case-th.patch (2.8 KB) - added by mikhail.vorozhtsov 21 months ago.
\case-expressions, patch for the template-haskell library
one-arg-lambda-case-testsuite.patch (5.8 KB) - added by mikhail.vorozhtsov 21 months ago.
\case-expressions, patch for the testsuite
multi-way-if.patch (23.5 KB) - added by mikhail.vorozhtsov 21 months ago.
MultiWayIf, patch for the GHC
multi-way-if-th.patch (5.2 KB) - added by mikhail.vorozhtsov 21 months ago.
MultiWayIf, patch for the template-haskell library
multi-way-if-testsuite.patch (7.5 KB) - added by mikhail.vorozhtsov 21 months ago.
MultiWayIf, patch for the testsuite

Download all attachments as: .zip

Change History (101)

Changed 4 years ago by batterseapower

Changed 4 years ago by batterseapower

comment:1 Changed 4 years ago by igloo

The motivating examples seem to be of the form

getArgs >>= case of
            [] -> error "No args"
            fs -> doit fs

so I wonder if a better extension would be:

mcase getArgs of
[] -> error "No args"
fs -> doit fs

comment:2 follow-up: Changed 4 years ago by malcolm.wallace@…

I would disagree that monadic bindings are the only (or even a major) use case.
Personally, I would often like the ability to have a standard lambda that can discriminate patterns on the value being bound.

foo = map (case of Alt1 a -> burble a
                   Alt2 | (x:xs) <- something    -> something more
                   otherwise -> def)

Lambda-case is more general than mcase.

comment:3 Changed 4 years ago by simonmar

+1 for lambda-case and lambda-if.

In a similar vein, I'd like to have multi-way if:

case | x < y     -> ...
     | x == y    -> ...
     | otherwise -> ...

comment:4 follow-up: Changed 4 years ago by simonmar

hmm, on second thoughts I revise that to +0 for lambda-case and lambda-if. They save a constant number of tokens (4), but the downside is that we have to adjust our mental parsers/typecheckers to recognise if then and case of as a lambda, and I'm not sure the gain in brevity is worth the loss of readability. (note that multi-way if doesn't suffer from this problem, it's just an abbreviation).

comment:5 in reply to: ↑ 2 Changed 4 years ago by igloo

Replying to malcolm.wallace@…:

I would disagree that monadic bindings are the only (or even a major) use case.

By "The motivating examples" I meant "The motivating examples in the thread linked to".

Personally, I would often like the ability to have a standard lambda that can discriminate patterns on the value being bound.

Do you have any realworld examples, OOI?

From a style point of view, using it point-free, as in your example, I think looks OK, but if it was being applied to something non-trivial (or even being composed with something non-trivial) I think it would be clearer if written using a let-bound function.

comment:6 Changed 4 years ago by hydo

  • Cc hydo@… added

comment:7 Changed 4 years ago by hydo

  • Cc cmoore@… added

comment:8 in reply to: ↑ 4 Changed 4 years ago by isaacdupree

Replying to simonmar:

hmm, on second thoughts I revise that to +0 for lambda-case and lambda-if. They save a constant number of tokens (4), but the downside is that we have to adjust our mental parsers/typecheckers to recognise if then and case of as a lambda, and I'm not sure the gain in brevity is worth the loss of readability. (note that multi-way if doesn't suffer from this problem, it's just an abbreviation).

"They save a constant number of tokens (4)" -- this is true, but the overhead is a bit more than just the tokens "\" "x" "->" (case) "x" (of ...). There's the mental complication of that name "x". You need to think, reading it: Is 'x' used anywhere later under the case/lambda?(no). And writing it: Are there variables named 'x' that we're accidentally shadowing, or otherwise using the same name confusingly?

I'm open to other suggestions than lambda-case/lambda-if that would help this mental-overhead issue.

comment:9 Changed 4 years ago by simonpj

  • Description modified (diff)

comment:10 Changed 4 years ago by simonpj

There is a lot of traffic about this on Haskell Cafe http://www.mail-archive.com/haskell-cafe@haskell.org/index.html#82264. There seems to be enthusiasm, but also plenty of discussion of variants and alternatives.

My own gut feel is that it's a lot better to have an initial symbol to say "here comes a function". So I'm keener on the multi-clause lambda idea, if someone can devise a syntax that works. GHC's internals assume a multi-clause lambda anyway, so it's more or less just a syntactic issue.

Simon

comment:11 Changed 4 years ago by simonpj

Chatting to Simon we came up with this syntax:

  \case { blah }  ==>   \x -> case x of { blah }

Things to note:

  • No syntactic collision: at the moment a lambda cannot be followed by the keyword case.
  • The form (a) denotes a function, and (b) starts with a lambda. That's consistent with Haskell today.
  • The \case would be a "layout herald" (ie starts a laid-out block), just as of is now. (A variant would be to use \of instead of \case; then of is the layout herald in both cases.)
  • The syntax of { blah } is precisely that of a case expression, guards and all. Nothing new to explain.

I rather like this. I can't think why we didn't think of it before.

Simon

comment:12 Changed 4 years ago by igloo

\ of is my favourite of the lambda-case syntaxes, although I still think mcase would provide more benefit (no reason we can't have both, though).

comment:13 Changed 4 years ago by batterseapower

Another variant would be something like this:

\case { (Just x) (Just y) -> x + y; _ _ -> 1 }  ==> \a b -> case (a, b) of { (Just x, Just y) -> x +y; (_, _) -> 1 }

This syntax would give us multi-argument lambdas that do arbitrary pattern matching, without problematic syntax like this:

\(Just x) (Just y) -> x + y; _ _ -> 1

However, it does mean that we would have to parse rather differently than a standard case. For example this would not be valid:

\case { Just x -> 1 }

Because it would parse as a two argument function with the second argument bound to x and the first argument bound to the invalid name Just.

comment:14 Changed 3 years ago by igloo

  • Milestone set to 7.2.1

comment:15 Changed 3 years ago by liyang

  • Cc hackage.haskell.org@… added

comment:16 Changed 3 years ago by uzytkownik

  • Cc uzytkownik2@… added

comment:17 Changed 3 years ago by spl

  • Cc leather@… added

comment:18 Changed 3 years ago by illissius

  • Cc illissius@… added

the downside is that we have to adjust our mental parsers/typecheckers to recognise if then and case of as a lambda, and I'm not sure the gain in brevity is worth the loss of readability

My own gut feel is that it's a lot better to have an initial symbol to say "here comes a function".

I don't think I agree with this. Maybe the word 'lambda' in the name of the proposal is misleading; I don't think the intuition here should be that of a lambda. I think it should be (and was originally) partial application. I think it's very natural for any Haskell programmer that if you have a function, and you apply it less than fully, that what you end up with is a function. The only difference here is that the argument sits in between the 'case of' rather than after the name of the function, but I don't think that's a big obstacle; it's universally obvious that case-of is special. I think the originally proposed 'case of' syntax is elegant and intuitive.

I'm much more ambivalent about if-then-else. It would make sense for completeness's sake, but it's a lot uglier (due to having multiple unorthodoxly-positioned arguments) and I don't see nearly as much of a use case. (It also, unlike 'case of', doesn't feel intuitive -- this is just a gut feel and I don't really have any arguments to explain why, but then again, the only real definition of intuitivity is whether people feel that way...) I think I'd rather just put ifThenElse in the Prelude and have people use that if they want to partially apply it (which isn't possible with case-of).

comment:19 Changed 3 years ago by mikhail.vorozhtsov

I ported Max's patch to (one-argument) \case syntax. I haven't touched alternative layout rules as I'm not familiar with them. Please review.

This syntax can be later extended to handle multiple arguments:

\case
  Just x, Just y -> e1
  Just x, _      -> e2

which would be translated to

\a b -> case (a, b) of
  (Just x, Just y) -> e1
  (Just x, _)      -> e2

What do you think?

comment:20 Changed 3 years ago by mikhail.vorozhtsov

  • Cc mikhail.vorozhtsov@… added

comment:21 Changed 3 years ago by simonmar

I think it would be disappointing if we couldn't do multi-argument functions this way. I like Max's suggestion in comment:13:

\case { (Just x) (Just y) -> x + y; _ _ -> 1 }  ==> \a b -> case (a, b) of { (Just x, Just y) -> x +y; (_, _) -> 1 }

Admittedly the syntax differs from a case expression, which could be confusing. So perhaps it should be \let instead? Or maybe we should just bite the bullet and pick a new keyword, e.g. fun?

comment:22 follow-up: Changed 3 years ago by simonpj

Another idea:

  • With LambdaCase, make plain "\" into a layout herald.
  • Allow multiple arguments as in Simon's comment above.

Something like \x y -> x+y would expand to \{x y -> x+y} so existing code would mostly be fine, even with the extension on. But not all existing code:

   f x $ \y ->
   g y $ \z ->
   blah

but maybe we don't care. With the extension on you could still use explicit curlies:

   f x $ \{y ->
   g y $ \{z ->
   blah }}

We don't like lambda-if, so let's not do that yet.

Then mif and mcase can be coded reasonably easily

do { ....
   ; doesFileExist "foo" >>=
     \ True -> do { ... file does exist ... }
       False -> do { ...it doesn't }
   ; ... }

comment:23 in reply to: ↑ 22 Changed 3 years ago by mikhail.vorozhtsov

Replying to simonpj:

>    f x $ \y ->
>    g y $ \z ->
>    blah

Scary. It happens that I'm doing exactly that (replace $ with >>=ᵢ).
I also think that \ is just too "small" of a token to start a new layout.
I remember someone on the list suggesting the following syntax:

\ p1, p2 -> e1
| p3, p4 -> e2

which I find more graphic.

Why don't we just have both, as different extensions (MultiClauseLambda)? The original proposal is about writing a good ol' case and letting the compiler sugar a lambda in, and one-arg \case is a perfect fit for the job.

At times like this I wish one could just upload a "plugin" package to Hackage and let the people decide...

comment:24 Changed 3 years ago by simonmar

Having re-read the Haskell cafe thread and after discussing this with Simon yesterday, I'm convinced that the multi-clause lambda approach is the cleanest extension of the language.

  • \case is out: either it supports a single-argument lambda only, which would be inconsistent with \, or it supports multi-argument lambda, which would be inconsistent with case.
  • | to separate clauses is out: | is already used for guards, so this is an inconsistency
  • , to separate arguments is out: nowhere else in the syntax uses comma to separate arguments
  • case of is out: it doesn't look like a lambda (I don't buy the partial application argument of comment:18, since for there to be a partial application there has to be a function, and there isn't one here)

In favour of multi-clause lambda:

  • it is more generally useful than mcase or case<-.

Against:

  • It is a change to the syntax. But it will be optional, so it only breaks existing code if you explicitly enable it.

Arguably having \ introduce layout is an improvement, because it enforces the requirement that the lambda binding scopes over code to the right and below.

There is one alternative design that does make sense: leave \ as it is and choose a new keyword for multi-clause lambda, e.g. fun (or, cheekily, λ). This would have the disadvantage that there would be two constructs for anonymous functions (too much syntax), I think we should try out multi-clause \ first and see how well it works in practice.

comment:25 follow-up: Changed 3 years ago by rl

Do I understand correctly that the RHS of

f = \x -> x
  z

would be parsed as (\x -> x) z with the extension?

comment:26 in reply to: ↑ 25 Changed 3 years ago by simonmar

Replying to rl:

it would parse as

f = \ { x -> x
 } z

which is a syntax error.

comment:27 Changed 3 years ago by rl

Sorry, I meant

f = \x -> x
  $ z

I would expect this to parse as (\x -> x) $ z with the extension, analogously to, say,

f = case x of x -> x
  $ z

But this would mean that the extension might actually silently change the meaning of existing code rather than breaking it which seems quite undesirable.

comment:28 Changed 3 years ago by simonmar

Correct, it will parse as (\x -> x) $ z, and indeed this might silently change the meaning of existing code. It's unlikely in this case, because x and (\x -> x) will have different types, but you could probably construct an example where the types are the same.

case and do already behave like this, whereas let and if behave the other way (munching up the $ z). The multi-clause lambda extension would move \ from the latter group to the former.

comment:29 Changed 3 years ago by rl

FWIW, I don't like the layout approach, especially given the above. I somewhat wonder if this would ever be ambiguous (layout doesn't matter)?

    \p1 p2 -> e1
    \p3 p4 -> e2

comment:30 Changed 3 years ago by simonmar

I can't think of any ambiguities with that, but it feels a bit strange, I suppose because it looks like two separate lambda expressions.

comment:31 follow-up: Changed 3 years ago by claus

Some of the syntax suggestions here scare me. But even if an unproblematic syntax could be found, do you really want to add a complex language construct with limited usage pattern? One nice thing about Haskell is that it tries to keep code programmable/library-based, with only minimal support through syntax built-ins.

I would like a 'case'-as-a-function variant, especially if it is more basic than the current 'case-of' (ie, the 'case-of' can be defined in terms of 'case', so the language does not get any bigger, just more flexible).

While I'm here: have you looked at introduce lambda-match (explicit match failure and fall-through), from around the same time as lambda-case? At the time, it was implemented as a small parser modification, with most of the functionality in a library (see ticket attachments for patch, library, and some examples), and 'case'-as-a-function was easy to implement in terms of it. But it also supported other useful programming patterns.

comment:32 Changed 3 years ago by lelf

  • Cc anton.nik@… added

comment:33 in reply to: ↑ 31 ; follow-up: Changed 3 years ago by simonmar

Replying to claus:

Some of the syntax suggestions here scare me. But even if an unproblematic syntax could be found, do you really want to add a complex language construct with limited usage pattern?

I'm really surprised by both "complex language construct" and "limited usage pattern" here, since those are both issues that I thought the proposal addresses quite well.

  • complex: we're re-using existing syntax and translations. The specification should be entirely straightforward (thought admittedly I haven't tried writing it down yet). The syntax is consistent with the rest of the language; it seems to me a natural generalisation. It's just an anonymous version of multi-equation function definitions, and the syntax is exactly what you'd expect.
  • limited usage pattern: it covers all the use cases that came up in the discussion: lambda-case, lambda-if (both with multiple arguments) and also mcase and mif.

While I'm here: have you looked at introduce lambda-match (explicit match failure and fall-through), from around the same time as lambda-case? At the time, it was implemented as a small parser modification, with most of the functionality in a library (see ticket attachments for patch, library, and some examples), and 'case'-as-a-function was easy to implement in terms of it. But it also supported other useful programming patterns.

Yes, I remember it, though I'd have to go and review the discussion to fully understand the motivation. On the face of it the syntax looks a bit strange (vertical bar is re-used).

comment:34 in reply to: ↑ 33 Changed 3 years ago by claus

Replying to simonmar:

I'm really surprised by both "complex language construct" and "limited usage pattern" here, since those are both issues that I thought the proposal addresses quite well.

"complex", as in made of many parts. Perhaps "monolithic" would be more to the point. As for "limited usage pattern", I am not doubting the use case for lambda-case (I'd use it myself). I am merely concerned that we have a growing number of complex/monolithic language constructs, with partially overlapping feature sets. Each of those language constructs covers one (class of) use case(s), but we cannot keep adding language constructs for each new use case.

If we could instead work out and expose the building blocks for these language constructs, then a small set of building blocks could cover all use cases. In other words, I'd like to see reuse not just by copy-and-modify of "existing syntax and translations", but by having language constructs that cover the shared features, with more complex language constructs being defined in terms of those simpler constructs (defining case-of in terms of case-as-a-function might be the most direct way to achieve that, as far as this ticket is concerned, but case-of really wants to be broken into smaller parts in the long run).

.. lambda-match (explicit match failure and fall-through)..

Yes, I remember it, though I'd have to go and review the discussion to fully understand the motivation. On the face of it the syntax looks a bit strange (vertical bar is re-used).

Motivation in brief, in the context of this ticket: instead of turning monolithic case into a function, turn each case alternative into a function, and provide the means to recombine the alternatives.

It was really hard to find non-ambiguous syntax - the vertical bar was to remind of algebraic datatype alternatives, but |pattern->body alone had a corner case of ambiguity, so it became (|pattern->body) instead. Adding guards back in ended up with (|pattern | guard -> body). That wasn't ideal, but read fairly well when using lambda-matches as case alternatives.

I don't think it is necessary to go that way for this particular ticket, but those who are showing interest in this ticket might find lambda-match interesting as a next step.

comment:35 Changed 3 years ago by simonpj

It's true that the proposal we outline above is monolithic in the sense that it's not composed of smaller parts. But it is particularly simple in another way: every part of GHC from the parser onwards already implements lambda-case! The matching part of a lambda, of a case expression, and of a multi-case function definition, are all defined using a MatchGroup (see HsExpr.lhs). All that lambda-case does is provide concrete syntax to let you get at the abstract syntax that is already there. So it just "fills the gap" as it were.

A more compositional approach to pattern matching is indeed an interesting topic. There have been a number of approaches propoed including yours. (Barry Jay's book is a more radical take on the subject.) But there isn't a clear winner yet.

So I think lambda-case is a fine candidate to fill the syntactic gap for now, leaving something more ambitious for the future.

Simon

comment:36 Changed 3 years ago by mikhail.vorozhtsov

Pretty printing gets somewhat ugly because we need to treat the first Match differently.

comment:37 Changed 3 years ago by simonmar

  • Milestone changed from 7.4.1 to 7.2.1
  • Priority changed from normal to high

Patch looks great, thanks Mikhail! We just need a test, and we can put it into 7.2.1.

comment:38 Changed 3 years ago by claus

I'd vote for requiring explicit braces for multi-clause-lambdas - better than breaking so much \-code. Perhaps ask for feedback on ghc-users, or where the mailing list threads were?

comment:39 Changed 3 years ago by simonmar

Frankly there's already been a great deal of feedback - this particular design was actually discussed in the most recent haskell-cafe thread and got a fair few +1s. I think it's now time to put it in and see how well it works in practice.

Remember, no code will actually break unless you explicitly ask to turn on the extension.

comment:40 Changed 3 years ago by mikhail.vorozhtsov

Fixed a bug, added compatibility with WarnIncompletePatterns? (multi-clause lambdas now are treated as case expressions in that regard).

comment:41 Changed 3 years ago by mikhail.vorozhtsov

I think we should use a different symbol/keyword after all. The new layout rule is great for multi-clause abstractions, but often gets in the way in the single-clause case, e.g.

mask $ \restore -> do
  blah
  blah
  blah

needs to be rewritten as

mask $ \
  restore -> do
  blah
  blah
  blah

comment:42 Changed 3 years ago by mikhail.vorozhtsov

Added support for 'where' bindings in alternatives. What about guards?

comment:43 Changed 3 years ago by mikhail.vorozhtsov

Enabled guards.

comment:44 Changed 3 years ago by igloo

  • Status changed from new to patch

comment:45 Changed 3 years ago by igloo

  • Owner set to igloo

comment:46 Changed 3 years ago by igloo

  • Milestone changed from 7.2.1 to 7.4.1

comment:47 Changed 3 years ago by mikhail.vorozhtsov

comment:48 follow-up: Changed 3 years ago by simonpj

Thanks for rebasing. The only issue here is one of syntax. Several people have expressed anxiety about making so quiet a symbol as "\" into a layout herald, and have showed that some existing code might break.

So, what to do? One possibility is not to make "\" into a symbol introducing implicit layout, but still to allow explicit layout with braces and semi-colons. Thus:

   \ { True -> e1; False -> e2 }

would be ok. But this would not be ok

    \ True -> e1
      False -> e2

That seems like a reasonable compromise. Using the brace/semicolon stuff is compatible with the way case expressions work, which is a Good Thing. Any comments?

Simon

comment:49 in reply to: ↑ 48 Changed 3 years ago by mikhail.vorozhtsov

Replying to simonpj:

Thus:

   \ { True -> e1; False -> e2 }

would be ok. But this would not be ok

    \ True -> e1
      False -> e2

That seems like a reasonable compromise. Using the brace/semicolon stuff is compatible with the way case expressions work, which is a Good Thing. Any comments?

Ugh. This is getting uglier and uglier. I've been using the current version of the patch for some time and I think the syntax has two major problems:

  1. It rejects the nice-looking indentation we are all used to in the single-clause case (see my 'mask' example above).
  1. It looks like a case expression, but has different pattern syntax, i.e. I constantly write
    foo >>= \
      Nothing -> ...
      Just a  -> ...
    

instead of the correct

foo >>= \
  Nothing  -> ...
  (Just a) -> ...

Explicit layout solves the first issue, IMO, at the expense of graphic clarity. I mean

foo >>= \
  { Nothing  -> do
      abc
      def
  ; (Just a) -> do
      abc
      def
  }

just makes my eyes hurt. Do we really want a feature that is only accessible using explicit layout?

I think the fact that single- and multi-clause lambdas require different indentation rules (and we can't look ahead to distinguish them) tells us to use a different token (\fn, \fun, \let?). I'd also say that \x -> case x of ... (or x <- foo; case x of ...) is such a common idiom that it deserves its "anonymous" counterpart (with respect to pattern syntax) regardless of the whole multi-clause lambda thing.

comment:50 follow-up: Changed 3 years ago by simonpj

Concerning (1) I don't understand. If you don't put a curly brace, everything would happen just like now... so mask would be fine.

Concerning (2), yes, I am suggesting that this feature is only accessible using explicit layout. It's not perfect, but the alternatives seem worse to me.

Does anyone else have other concrete proposals? If so, let's hear them.

The default alternative is to do nothing.

Simon

comment:51 in reply to: ↑ 50 Changed 3 years ago by mikhail.vorozhtsov

Replying to simonpj:

Concerning (1) I don't understand. If you don't put a curly brace, everything would happen just like now... so mask would be fine.

Yes. I was referring to the syntax implemented in the patch.

Does anyone else have other concrete proposals? If so, let's hear them.

Let me bug you one last time. Please consider adding \case expressions in addition to multi-clause lambdas. Сase expressions are at the fingertips of every haskeller, not having their "anonymous" version will (and in my case already is) lead to constant stumbling (due to different pattern syntax). Besides, we already have that sort of duplication for one-argument named functions (fun P = ... vs fun x = case x of P -> ...).

comment:52 Changed 3 years ago by simonmar

I don't like the explicit layout idea. The goal so far has been to find a way to allow multi-clause multi-argument anonymous functions but without adding any inconsistencies to the language. Having \ introduce a layout context fits the bill, but unfortunately it breaks many common idioms.

Perhaps the only way to do this is to have a new keyword.

comment:53 Changed 3 years ago by mikhail.vorozhtsov

We could use "\" + "map" as the starting lexemes. It is uncommon to name an argument "map" due to the overshadowing warning. Of course, in all other contexts "map" would be just an identifier.

comment:54 Changed 3 years ago by zzo38

I suggest using "case of" for the lambda-case. For two arguments, you can use "curry case of"

comment:55 Changed 3 years ago by igloo

  • Milestone changed from 7.4.1 to 7.6.1

Punting

comment:56 Changed 2 years ago by dterei

  • Cc dterei added

comment:57 Changed 2 years ago by jeltsch

  • Cc g9ks157k@… added

comment:58 Changed 2 years ago by jeltsch

It would be good if we had something analogous for proc from arrow notation. This seems to rule out the partial application solution case of …, but seems to go well with the multi-clause lambda, since we could also have a multi-clause proc.

comment:59 Changed 2 years ago by simonpj

  • Difficulty set to Unknown
  • Owner igloo deleted
  • Priority changed from high to normal
  • Status changed from patch to new

Although we have a concrete proposal here for multi-clause lambdas and lambda-case, and a patch, we just aren't yet convinced enough that the (modest) increase in language complexity is justified by the (modest) benefits. Especially as some common idioms using lambda no longer work with the change. This isn't a definite "no", but we'll downgrade the priority until we can find a better solution.

comment:60 follow-up: Changed 2 years ago by mikhail.vorozhtsov

It would be reasonable to split multi-clase-lambdas into a separate ticket, so that each feature can be discussed in a clearer context of pros and cons (e.g. lambda-case does /not/ break any idioms). What do you think?

I've been using lambda-case at work for a year and never looked back to tmp1,tmp2,...[1] But patching GHC every time /is/ irksome. I even started to think about replacing GHC lexer/parser with modular (combinators-based) analogs, to allow compiler plugins to hook into parsing.

[1] I write a lot of code like this:

await (event1 .?. event2 ... eventN) >>= \case
  (elem0 -> Just v) -> ...
  (elem1 -> Just v) -> ...
  ...

If you do it without \case, temporary bindings can easily go into tmp10+ in a single function.

comment:61 Changed 2 years ago by simonpj

Thanks. Yes, I think that part of the difficulty is that this ticket is 60 comments long; it dicusses more than one feature; for each feature it discusses more than one design; and Simon and I are deeply under water with other aspects of GHC, which makes it hard to devote attention to new features.

So yes, start a new ticket. Write a wiki page that explains the proposal you actually advocate (give links to other competing proposals you rejected). Explain pros and cons. Add a link from http://hackage.haskell.org/trac/ghc/wiki/Commentary. Encourage comment on ghc-users.

I must say that \case... looks more attractive to me, because there is a clearly visible herald, using a keyword that otherwise cannot appear there. And that point had become submerged (for me) in the detail of this thread.

comment:62 in reply to: ↑ 60 Changed 2 years ago by jeltsch

Replying to mikhail.vorozhtsov:

It would be reasonable to split multi-clase-lambdas into a separate ticket, so that each feature can be discussed in a clearer context of pros and cons (e.g. lambda-case does /not/ break any idioms).

If you open a separate ticket for this, then please link to it here, so that I get notified about this new ticket.

comment:63 follow-up: Changed 2 years ago by simonmar

Rather than a single new proposal, I think what would be most useful would be a summary of the previous proposals and their pros and cons, so that we don't have to rediscover all this.

e.g. we previously didn't like \case because we wanted a way to have multiple arguments too: lambda currently allows multiple arguments, and \Just x -> is a function of two arguments, whereas \case Just x -> is a function of one argument. I'm not necessarily discounting the idea on this basis, but it is arguably an inconsistency.

Another alternative is to introduce a new keyword for anonymous multi-clause multi-argument functions, e.g. fun.

comment:64 in reply to: ↑ 63 Changed 2 years ago by jeltsch

Replying to simonmar:

Another alternative is to introduce a new keyword for anonymous multi-clause multi-argument functions, e.g. fun.

Note that when introducing a new construct for functions, we should introduce an analog construct for arrows. If we introduced a new keyword fun for functions, we would have to introduce another new keyword for arrows. If we would reuse \ as in \ case, we could reuse proc in the same way (proc case). This is not to say that I particularly like the proposal with \ case, but that the syntactic construct that we come up with should reuse \.

comment:65 Changed 22 months ago by mikhail.vorozhtsov

I just wrote a wiki page. Editing (my english sucks) and improvements are welcome.

comment:66 Changed 22 months ago by simonmar

Thanks for the wiki page, that helps a lot!

FWIW, OCaml has both \ and \case, called respectively fun and function. It does not have a multi-argument multi-equation anonymous function construct. (ref: http://caml.inria.fr/pub/docs/manual-ocaml/expr.html).

I'm hesitant to express any more opinions at this stage, but now that we've explored the design space quite thoroughly I'm feeling more inclined towards \case.

Changed 22 months ago by mikhail.vorozhtsov

Multi-clause lambda abstractions extension, v4, another rebase

Changed 22 months ago by mikhail.vorozhtsov

Tests for the MultiClauseLambdas extension, v2, another rebase

comment:67 Changed 22 months ago by mikhail.vorozhtsov

I started a new thead on the GHC Users list. Hop in!

comment:68 Changed 22 months ago by simonpj

  • Description modified (diff)

comment:69 follow-up: Changed 21 months ago by simonpj

Simon and I have had a chat, and discussed the various alternatives swirling around. In the interests of deciding something, rather than continuing an infinite loop of design choices, we propose to adopt:

  • \case, with exactly the same case alternatives as an ordinary case expression. That is, each alternative has a single un-parenthesised pattern. Thus
       \case { Just x -> x+1; Nothing -> 0 }
    
    The \case becomes a layout herald. It would be fine if the implementation does not permit any space between the backslash and the case; but if it's easier to allow space there, that's ok too. (Personally I'd strongly discourage programmers from using space there.)
  • Multi-branch if:
      if | x>0  -> -1
         | x==0 -> 0
         | otherwise -> 1
    
    No layout here; branches continue (as with guarded case alternatives) until the next "|", and the last branch continues as far as possible (like the else of an if).

There are lots and lots of other alternatives, but these choices seem simple and achieve much of the benefit. If anyone thinks this is so bad that it's worth continuing the debate, sing out.

mikhail, if you'd like to send a patch (don't forget documentation!) we'll apply it. Many thanks.

Simon

comment:70 Changed 21 months ago by mikhail.vorozhtsov

\case is already implemented in the attached one-arg-lambda-case.patch file. I'll try to implement MultiWayIf this weekend. BTW, we are running out of bits in the extensions bitmap on 32-bit platforms (\case occupies 30th bit and MultiWayIf will get 31st).

comment:71 in reply to: ↑ 69 Changed 21 months ago by jeltsch

Replying to simonpj:

\case, with exactly the same case alternatives as an ordinary case expression. That is, each alternative has a single un-parenthesised pattern. Thus \case { Just x -> x+1; Nothing -> 0 }

Could this extension please also cover proc case then? Note that currently the situation for arrow programming is even worse than for “ordinary” programming. If the case distinction happens at the outermost point of a definition, then in the case of “ordinary” programming, you have the possibility to use pattern matching on the left hand sides of equations like so:

f pat1 = rhs1
f pat2 = rhs2
...

But in the case of arrow programming, you don’t have such a possibility, so that you have to resort to something like this:

f = proc x -> case x of
        pat1 -> rhs1
        pat2 -> rhs2
        ...

A proc case would be of great help here.

The \case becomes a layout herald. It would be fine if the implementation does not permit any space between the backslash and the case; but if it's easier to allow space there, that's ok too.

For proc case, the space would be mandatory. So it would make sense to also allow space between \ and case, that is, treat \ and case as two different tokens. I don’t know if it’s common to treat a group of two tokens as a layout herald. If not, then it might be better to use \ of instead of \ case, since the single of is already a layout herald.

(Personally I'd strongly discourage programmers from using space there.)

I have the exact opposite opinion. In the case of ordinary lambda expressions, it is sometimes necessary to put a space after the \, namely, if the \ is followed by a ~. So I think it’s a good idea to always insert a space after \ to have a consistent style. This should probably carry over to \ case then.

Multi-branch if: if | x>0 -> -1 | x==0 -> 0 | otherwise -> 1

It would be great to also have an arrow version of this. This would have arrow commands instead of expressions after the ->’s, and would itself be an arrow command.

comment:72 follow-up: Changed 21 months ago by simonmar

I think it would be a mistake to treat \case as a single lexeme, because it doesn't fit at all well with the way the lexical syntax is presented in the Haskell report.

I think it might be better to implement \case using a lexer state rather than tracking the previous token in the monad as the patch currently does. But it's hard to say for sure without trying it.

comment:73 follow-up: Changed 21 months ago by simonpj

Concerning arrows, I do agree that (proc case alts) as syntactic sugar for (prox x -> case x of alts) would be consistent. I don't know the arrow equivalent of multi-branch if would look like. Want to flesh it out?

However I am deeply reluctant to fiddle with the arrow code at all; there's a long-standing plan to refactor the arrow-handling code which is currently complex and crufy. If anyone arrow-keen person is willing to undertake this, I am happy to help. The ticket is #7071.

Simon

comment:74 in reply to: ↑ 72 ; follow-up: Changed 21 months ago by mikhail.vorozhtsov

Replying to simonmar:

I think it might be better to implement \case using a lexer state rather than tracking the previous token in the monad as the patch currently does. But it's hard to say for sure without trying it.

I'm no alex expert, so I don't see how it can be implemented with states. The logic here seems to be:

  1. On \, return ITlam and enter a new state, let's say, AFTER_LAM.
  2. Skip whitespace/comments (works for all states).
  3. If the next token is case, then return ITlcase and start a new layout.
  4. If the next token is not case/whitespace/comments, then switch to state 0 and return the token.

How to implement (4)?

comment:75 in reply to: ↑ 73 Changed 21 months ago by jeltsch

Replying to simonpj:

Concerning arrows, I do agree that (proc case alts) as syntactic sugar for (prox x -> case x of alts) would be consistent. I don't know the arrow equivalent of multi-branch if would look like. Want to flesh it out?

And example using it would look like this:

proc x -> if | x >  0    -> f -< -1
             | x == 0    -> g -< 0
             | otherwise -> h -< 1

However I am deeply reluctant to fiddle with the arrow code at all; there's a long-standing plan to refactor the arrow-handling code which is currently complex and crufy. If anyone arrow-keen person is willing to undertake this, I am happy to help. The ticket is #7071.

I can imagine to help here, but I would like to be permitted to add support for arrow versions of lambda case and multi-branch if.

comment:76 in reply to: ↑ 74 Changed 21 months ago by simonmar

Replying to mikhail.vorozhtsov:

How to implement (4)?

In Alex, () matches the empty string. So you want something like

<after_lam>
  @varid    { check_for_case }
  ()        { pop the after_lam state and call lexToken }

comment:77 Changed 21 months ago by simonpj

Wolfgang (I think): yes, if you do the refactoring of #7071 you will have a massive GHC credit balance with which to add proc case and multi-way if :-).

Simon

comment:78 follow-up: Changed 21 months ago by igloo

The lexer complexity is only an issue with the \ case syntax, not with either \ of or case of, right?

So given opinion seems to be divided on which is aesthetically nicer, which don't we implement one of the syntaxes that gives a simpler, cleaner specification and implementation?

comment:79 in reply to: ↑ 78 Changed 21 months ago by jeltsch

Replying to igloo:

The lexer complexity is only an issue with the \ case syntax, not with either \ of or case of, right?

So given opinion seems to be divided on which is aesthetically nicer, which don't we implement one of the syntaxes that gives a simpler, cleaner specification and implementation?

It’s not just about aesthetics, specification, and implementation. The case of solution has the problem that it cannot be carried over to arrow expressions. So we have the following situation:

case of
no analog for arrow expressions
\case
lexer complexity
\of
not immediately visible that it is about case distinction

I tend to prefer \of.

comment:80 follow-up: Changed 21 months ago by igloo

I don't see a major problem with case of / proc of, although personally I would also prefer \ of / proc of.

comment:81 Changed 21 months ago by mikhail.vorozhtsov

I almost got lexer state working, but then I found that it gets in the way of layout handling. Assuming we have

<after_lambda> {
  @varid    / { isCaseKeyword } { lambda_case }
  ()        { pop }
}

the following code will not be parsed correctly

f3 = \ -- comment
       case "1" -> 1
            "2" -> 2

because there is a newline between \ and case. And newline handling depends on the previous lexer state in the stack. I probably could write a lexer action that checks it (whether we are in do-expression/etc) and does the right thing (check offsets, insert ;, ...), but IMO it would be much uglier and error-prone than keeping track of the last non-comment token. Any thoughts?

comment:82 Changed 21 months ago by simonmar

Ok, I guess we have to keep track of the previous token. Thanks for trying it anyway.

comment:83 in reply to: ↑ 80 Changed 21 months ago by jeltsch

Replying to igloo:

I don't see a major problem with case of / proc of, although personally I would also prefer \ of / proc of.

Since proc is the arrow analog of \, I think we should use a syntax that uses \ in the notation for ordinary lambda case and uses proc instead of \ for the arrow lambda case.

Changed 21 months ago by mikhail.vorozhtsov

\case-expressions, patch for the GHC

Changed 21 months ago by mikhail.vorozhtsov

\case-expressions, patch for the template-haskell library

comment:84 Changed 21 months ago by mikhail.vorozhtsov

Ok, here is the latest version of the \case patch. I added Template Haskell support and a few boring tests for the testsuite. The only remaining issue is addTickHsExpr, I think my naive version doesn't do exactly the right thing with TickAllFunctions. Could someone take a look?

Changed 21 months ago by mikhail.vorozhtsov

\case-expressions, patch for the testsuite

Changed 21 months ago by mikhail.vorozhtsov

MultiWayIf, patch for the GHC

Changed 21 months ago by mikhail.vorozhtsov

MultiWayIf, patch for the template-haskell library

Changed 21 months ago by mikhail.vorozhtsov

MultiWayIf, patch for the testsuite

comment:85 Changed 21 months ago by mikhail.vorozhtsov

I've attached MultiWayIf patches. They are supposed to be applied on top of the \case patches.

comment:86 Changed 21 months ago by simonmar

  • Owner set to simonmar
  • Priority changed from normal to high

I'm validating.

comment:87 Changed 21 months ago by simonmar

  • Resolution set to fixed
  • Status changed from new to closed

All committed, these will be in 7.6.1. Thanks everyone!

comment:88 Changed 21 months ago by jeltsch

There is now a new feature request #7081 for arrow analogs of lambda case and multi-way if. This also covers an arrow command analog of lambda case in addition to the proc expression analog.

comment:89 follow-up: Changed 20 months ago by guest

Indendation problem:

{-# LANGUAGE MultiWayIf #-}
x = if | False -> if | False -> 1
                     | False -> 2
       | True -> 3

should be 3, but currently is parsed as:

{-# LANGUAGE MultiWayIf #-}
x = if | False -> if | False -> 1
                     | False -> 2
                     | True -> 3

which fails.

comment:90 in reply to: ↑ 89 Changed 20 months ago by mikhail.vorozhtsov

Replying to guest:

Indendation problem:

[snip]

MultiWayIf is not layout-aware, see the comment #69.

comment:91 Changed 20 months ago by simonmar

We didn't realise the nesting problem with MultiWayIf until after it was added, and I now think it was a poor choice of syntax. The fact that someone reports this on the ticket as a bug indicates that it is problematic in practice too.

Note: See TracTickets for help on using tickets.