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

Whether support *: as a valid return type of unapply in extractors #11008

Closed
liufengyun opened this issue Jan 6, 2021 · 13 comments · Fixed by #14434
Closed

Whether support *: as a valid return type of unapply in extractors #11008

liufengyun opened this issue Jan 6, 2021 · 13 comments · Fixed by #14434

Comments

@liufengyun
Copy link
Contributor

The following code is currently not supported by the compiler:

object U:
  def unapply(s:String): String *: EmptyTuple = Tuple1(s)

"hello" match
case U(x) => 

If we change String *: EmptyTuple to Tuple1[String], it will compile without problem.

The problem is that according to the current specification, String *: EmptyTuple does not qualify as a product pattern, as it does not contain selectors _1, ..., _N.

The question is:

  • Whether we should support String *: EmptyTuple?
  • How to change the specification?

One particular concern is that making *: a special case seems to cancel the past efforts to make pattern match more generic.

Related PR: #10987

@nicolasstucki
Copy link
Contributor

nicolasstucki commented Jan 6, 2021

We could use the NonEmptyTuple.apply(N) instead of _N. This gets optimized to _N when the tuple is known to be implemented with a TupleN for N <= 22.

One particular concern is that making *: a special case seems to cancel the past efforts to make pattern match more generic.

This is part of a different dimension of genericity that we need to be able to handle as well.

@liufengyun
Copy link
Contributor Author

This is part of a different dimension of genericity that we need to be able to handle as well.

I agree, if we can find a way to change the specification without a special case for *:, that would be ideal.

@odersky
Copy link
Contributor

odersky commented Jan 6, 2021

Is there a strong reason why *: should be a valid return type? If not, I think we have now seen enough arguments why it should not.

@nicolasstucki
Copy link
Contributor

@liufengyun
Copy link
Contributor Author

The last two items seem to be not strong reasons. The first can be fixed by changing the return type. For the second, I don't think realistic code would write pattern match to deal with > 22 arguments.

@joroKr21
Copy link
Member

joroKr21 commented Jan 6, 2021

I'd add - support extractors for generic code.
For example I might want to derive an extractor the same way I can derive a type class.

@liufengyun
Copy link
Contributor Author

I'd add - support extractors for generic code.

@joroKr21 To check whether it affects the specification, could you be more specific about this with a simple example?

@joroKr21
Copy link
Member

joroKr21 commented Jan 7, 2021

@liufengyun I can give you a complex example of what I would like to try in Scala 3: recursion-schemes.scala

The idea is that for a recursive type (Expr) we can derive it's pattern functor ExprF[_] automatically. In ExprF[A] recursive occurrences of Expr are replaced by the type parameter A. We can also derive automatically conversions between Expr and the fixpoint ExprF[Fix[ExprF]]. Based on that we can build a library of recursion schemes (cata-, ana-, hylo-, para-, etc. morphisms).

But to use them we need to pattern match on ExprF[x] (for example ExprF[Int] in eval, ExprF[String] in show, etc.) which is not an ADT by itself - it's just a raw sum of products. In Scala 2 we can achieve that in a somewhat hacky way but it would be so much nicer if we can match directly on generic tuples (products) in Scala 3. We can already match directly on union types which can be used to represent sums.

I will think a bit about a simpler example.

@dwijnand
Copy link
Member

dwijnand commented Jan 7, 2021

How does Scala 3 support case List(a)? In Scala 2 it's supported by special-casing List in the match analysis to be rewritten as a :: Nil and then :: is already a 2-element case class.

So if Scala 3 generalised that into rewriting any case U(a) with an unapply with that return type into a *: EmptyTuple (and case U(a, b) => case a *: b *: EmptyTuple..) then it would be using *:.unapply:

object *: {
  def unapply[H, T <: Tuple](x: H *: T): (H, T) = (x.head, x.tail)
}

@liufengyun
Copy link
Contributor Author

How does Scala 3 support case List(a)? In Scala 2 it's supported by special-casing List in the match analysis to be rewritten as a :: Nil and then :: is already a 2-element case class.

Dotty has some similar logic in exhaustivity check:

https://github.com/lampepfl/dotty/blob/437d02ad7d2f582d25e57d86e70fda96b6e0a330/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala#L501-L503

However, this is just a trick in exhaustivity check: type checking and semantic translation don't have such things.

The following semantic rewrite seems to be problematic, as it throws U away:

case U(a, b) => case a *: b *: EmptyTuple

@liufengyun
Copy link
Contributor Author

In the meeting, it's decided that we need to support *: as a valid return type of unapply. The strong reason mentioned in the meeting is to support big data applications, where the arity usually goes over 22. And in that case user code may pattern match on tuples with arity > 22.

The initial plan is to add an ad-hoc rule in the specification of pattern matching. Typer, patternMatcher, exhaustivity check needs to be updated accordingly.

@SethTisue
Copy link
Member

I don't think realistic code would write pattern match to deal with > 22 arguments

This could become more likely if scala/improvement-proposals#44 goes through

@jbbrissaud
Copy link

jbbrissaud commented Nov 22, 2022

Please tell me if I post in the wrong place. In the following code:

class myClass[A<:Tuple]:
  def unapply(n:Int):Option[A] =
    None
  def add[B]:myClass[Tuple.Append[A,B]] =
    new myClass[Tuple.Append[A,B]] 
@main def test =
  val val1 = myClass[(Int,Double)]()
  val val2: myClass[(Int, Double, String)] = val1.add[String]
  42 match
    case val2(n,d,s) => println("")
    case _ => println("ok")

if I don't give the type for val2 in line 8, the compiler correctly infers its type, but extra parentheses are needed line 10 around (n,d,s).
It is a bug, I suppose.

@Kordyjan Kordyjan added this to the 3.1.3 milestone Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants