Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow named and default arguments in pattern matching #6524

Closed
scabug opened this issue Oct 15, 2012 · 21 comments
Closed

Allow named and default arguments in pattern matching #6524

scabug opened this issue Oct 15, 2012 · 21 comments
Labels
enhancement fixed in Scala 3 This issue does not exist in the Scala 3 compiler (https://github.com/lampepfl/dotty/) help wanted patmat

Comments

@scabug
Copy link

scabug commented Oct 15, 2012

Named and default arguments have been introduced in Scala 2.8, and were outlined in SIP #1. An important scenario has been overlooked in the SIP, which is to define interaction of named and default arguments with pattern matching cases. Adding support for named arguments in pattern matching improves Scala in two ways:

(1) It increases the regularity of the language in the sense that a feature (named arguments) is applicable to more scenarios, and case class constructor calls and pattern cases are more symmetrical
(2) It brings the benefits of named arguments to a widely used feature of Scala (pattern matching): Avoiding boiler plate, and avoiding mistakes when multiple arguments of the extractor have the same type.

Example:

    case class Advance(time: Long, isSeek: Boolean, isPlaying: Boolean, added: List[Any] = Nil, removed: List[Any] = Nil)

An instance of this class can be conveniently created such as this:

    Advance(time=0L, isSeek=false, isPlaying=true)

But a pattern match is only possible like this:

    def update(u: Any) = u match {
      case Advance(time, isSeek, isPlaying, added, removed) => ...
    }

With a real trap like the following:

    def update(u: Any) = u match {
      case Advance(time, isPlaying, isSeek, removed, added) => ...
    }

Or even this, if not all arguments are used and therefore undergoing successive type checks:

    def update(u: Any) = u match {
      case Advance(isSeek, isPlaying, time, _, _) => ...
    }

Cf. here

If named arguments were allowed, the following would be valid:

    def update(u: Any) = u match {
      case Advance(time = t, isSeek = false, isPlaying = true, removed = r, added = a) => ...
    }

Furthermore, if a "default" argument in an extractor is equated with _, the following would be valid:

    def update(u: Any) = u match {
      case Advance(isPlaying = true) => ...
    }

...and equivalent to...

    def update(u: Any) = u match {
      case Advance(time = _, isSeek = _, isPlaying = true, removed = _, added = _) => ...
    }

Cutting boiler plate in pattern match cases and make them more readable.

A problem I can see is where arguments are omitted but no named arguments used, such as:

    def update(u: Any) = u match {
      case Advance(time, isSeek) => ...
    }

In the constructor case this would be only legal if default arguments were given for isPlaying, added and removed, whereas in the deconstruction/extractor case it would be always legal if a default of _ is implied for each argument. Perhaps a compromise would be to allow default arguments only where named arguments are used?

@scabug
Copy link
Author

scabug commented Oct 15, 2012

Imported From: https://issues.scala-lang.org/browse/SI-6524?orig=1
Reporter: @Sciss

@scabug
Copy link
Author

scabug commented Oct 15, 2012

@hubertp said:
Duplicate of #4425 and #3353.

@scabug
Copy link
Author

scabug commented Oct 15, 2012

@Sciss said:
I don't see this as a duplicate; those are bugs which deal with default arguments for implicit parameters in the unapply method; i.e. with arguments supplied to the pattern matcher, not the results from the pattern matcher. Anyway, my main case is named arguments which is unrelated to those issues.

@scabug
Copy link
Author

scabug commented Oct 15, 2012

@hubertp said (edited on Oct 15, 2012 5:24:18 PM UTC):
Well, from the title of the bug that looks like 'named and default arguments'. And fixing those bugs means defining the correct behaviour for the things your want. So, they are definitely related (and I would say sort of duplicates).

See for example Lukas' comment on #3353:
"There's a whole space to explore on the interaction of named/default arguments and pattern matching. If we start doing this, we will end up with this kind of blocks during pattern matching (and btw, blocks don't need to have symbols)."

I didn't close this bug because you kind of rely on the bugs I mentioned. And I guess if we want to be formal SIP would be the proper way.

@scabug
Copy link
Author

scabug commented Oct 15, 2012

@Sciss said (edited on Oct 15, 2012 5:38:14 PM UTC):
Of course it is loosely related, because this issue (which is not a bug but a feature request!) and the two bugs linked both involve default arguments. But this:

    object Foo {
      object X { def unapply(x : Int)(y: Int) = Some((2,2)) }
      42 match { case _ X _ => () }
    }

has nothing to do with allowing named and default arguments in pattern match cases, unless I am missing something, and I'm trying hard to think of how they would be related, or how this feature request relies on those bugs? In the other example

    object X { def unapply(x : Int)(y : Option[Int] = None) = None }

the point seems to be allowing to match using an unapply method which has multiple parameter lists (explicit or implicit) for which defaults exists. How would fixing those bugs produce correct/desired behaviour in this case?

@scabug
Copy link
Author

scabug commented Oct 16, 2012

@hubertp said:
The first example has nothing to do with it, agreed. Still, the interaction between named and default arguments in pattern matching which is decided here will influence how the bugs I mentioned should be properly solved (and vice versa). We have enough mess in the bug tracker without that so anyone who deals with them should at least know of that connection.

@scabug
Copy link
Author

scabug commented May 21, 2013

@Sciss said:
For reference, here is the scala-debate thread. There are some valid points against this, particularly when custom extractors are used, there is no defined naming for the "arguments" (because they are the elements of the return tuple).

@scabug
Copy link
Author

scabug commented Jul 19, 2013

Shelby Moore III (shelby) said:
My thought is to prefer:

1. case Advance(time, isSeek)
2. case Advance(time, isSeek = false)
3. case Advance(t = time, isSeek = false)

instead of:

1. case Advance(time = time, isSeek = isSeek)
2. case Advance(time = time, isSeek = false)
3. case Advance(time = t, isSeek = false)

Named tuples may aid unification with custom extractors.

val t = new Tuple2(42,1){def first = _1; def second = _2}

t.first
res7: Int = 42

t.second
res8: Int = 1

@scabug
Copy link
Author

scabug commented Jul 24, 2013

Shelby Moore III (shelby) said (edited on Jul 24, 2013 2:03:40 AM UTC):
At the aforementioned discussion thread, one suggestion is to use == instead of = when matching a named parameter to a value:

2. case Advance(time, isSeek == false)
3. case Advance(t = time, isSeek == false)

This allows the order to be consistent:

2. case Advance(time, false == isSeek)
3. case Advance(t = time, false == isSeek)

I also suggested that default parameters only apply when no parameters are being extracted, i.e. there are only values specified for the parameters. This maintains the symmetry with constructors.

Should a SIP be created for this ticket?

@scabug
Copy link
Author

scabug commented Jul 24, 2013

@Sciss said:
The problem with == is that you can't distinguish it from a Boolean expression which is totally valid, too. Of course, the difference is that isSeek is an argument name. So this produces the same confusion as with catching symbols versus backtick escaping, e.g.

val isSeek: Boolean = ???

def m(a: Any) = a match {
  case Advance(t = time, { println("hi there"); false == isSeek }) => ...
}

To use the value, you would need to write false == isSeek``

Here is another suggestion, using the left-arrow from for-comprehensions:

case Advance(t <- time, false <- isSeek)

case Person(LegalToDrink(a) <- age)
case Person(ltd @ LegalToDrink(a) <- age)
case Person(ltd: LegalToDrink <- age)

The advantage of this IMO is that it doesn't introduce any new syntactic concept or variant, but is well established. On the other hand, in for-comprehensions the left-arrow indicates that the symbol is a looping variable, something that is not the case here.


A third suggestion would be to require guards for ==. Something like

    case Advance(t <- time) if !isSeek =>

One would then be free to use = instead of <-, e.g.

case Advance(t = time) if !isSeek =>

case Advance(val t = time) if !isSeek =>

@scabug
Copy link
Author

scabug commented Jul 25, 2013

Shelby Moore III (shelby) said (edited on Jul 25, 2013 4:01:29 AM UTC):
Sciss, I think the == named matching syntax (which is your idea from the aforementioned discussion group) can be distinguished from a boolean expression forming a unnamed matching value because the value of isSeek does not pre-exist in the former case. I prefer the == syntax because it is the more obvious (unified with pre-existing syntax) than for-comprehension left-arrow (to me at least) and more regular/unified/less verbose than guards.

P.S. Note the typo in the bug description; it should be SIP (Scala Improvement Process) not SID (document).

@lwouis
Copy link

lwouis commented Aug 25, 2017

What's the status of this feature?

@adriaanm
Copy link
Contributor

We currently have no plans to implement this. Since this involves a language change, it would require a SIP.

@scala scala deleted a comment from scabug Aug 27, 2018
@SethTisue
Copy link
Member

SethTisue commented Mar 10, 2021

This would be a great project for somebody who wants to tackle something a bit more ambitious (yet not fundamentally difficult).

This is something a lot of people have wanted for a long time, so any volunteer who appeared would get lots of support and encouragement.

Adding it to Scala 2 first, then forward-porting to 3, is a possibility, but it's equally plausible to target 3 first, then (optionally) backport. Note that we can't go forward with this in 2 unless the 3 team is on board.

@dwijnand
Copy link
Member

dwijnand commented Mar 10, 2021

@Sciss said:
For reference, here is the scala-debate thread. There are some valid points against this, particularly when custom extractors are used, there is no defined naming for the "arguments" (because they are the elements of the return tuple).

I haven't read the thread yet, but since 2013 two relevant things have happened:

  1. Scala 3 extended custom extractors to return Product ("Product Match" in https://dotty.epfl.ch/docs/reference/changed-features/pattern-matching.html); and
  2. we've added productElementNames to Product

So I think this might no longer be a blocker.

@SethTisue
Copy link
Member

@dwijnand
Copy link
Member

For reference, here is the scala-debate thread.

New link (though behind http-use warning): https://grokbase.com/t/gg/scala-debate/12ad0n33b0/default-and-named-arguments-in-extractors

@SethTisue
Copy link
Member

SIP: scala/improvement-proposals#44

@SethTisue
Copy link
Member

SethTisue commented Aug 22, 2024

closing as out of scope for Scala 2 (as the Scala 3 implementation ending up depending on named tuples, and backporting that is out of scope)

@SethTisue
Copy link
Member

SethTisue commented Aug 22, 2024

but in Scala 3, as of Scala 3.5.0 there is experimental support:

Welcome to Scala 3.5.0
                                                                                                    
scala> import language.experimental.namedTuples
                                                                                                    
scala> case class Person(name: String, age: Int)
// defined case class Person
                                                                                                    
scala> Person("Alice", 52) match
     |   case Person(age = age) => age
     | 
val res0: Int = 52

see https://www.scala-lang.org/blog/2024/08/22/scala-3.5.0-released.html

@SethTisue SethTisue removed this from the Backlog milestone Aug 22, 2024
@SethTisue SethTisue added the fixed in Scala 3 This issue does not exist in the Scala 3 compiler (https://github.com/lampepfl/dotty/) label Aug 22, 2024
@SethTisue
Copy link
Member

adding the "fixed in Scala 3" despite this being experimental, as it seems highly likely to make it out of experimental

@SethTisue SethTisue closed this as not planned Won't fix, can't repro, duplicate, stale Aug 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement fixed in Scala 3 This issue does not exist in the Scala 3 compiler (https://github.com/lampepfl/dotty/) help wanted patmat
Projects
None yet
Development

No branches or pull requests

5 participants