Scala Programming Language
  1. Scala Programming Language
  2. SI-6111

Extractors can't provide Tuples as a single parameter in a match

    Details

      Description

      There seems to be an issue when using a generic extractor and which could receive a tuple type. As soon as the generic type parameter in the result type is instantiated with a tuple type, the pattern matcher interprets it as multiple parameters in a match.

      object Foo {
        def unapply[S, T](scrutinee: S)(implicit witness: FooHasType[S, T]): Option[T] = scrutinee match {
          case i: Int => Some((i, i).asInstanceOf[T])
        }
      }
      
      class FooHasType[S, T]
      object FooHasType {
        implicit object int extends FooHasType[Int, (Int, Int)]
      }
      
      object Test {
        val x = 8
        x match {
          case Foo(p) => p // p should be a pair of Int
        }
      }
      

      This should compile. However, it instead results in the following error:

      <console>:21: error: wrong number of arguments for object Foo
                 case Foo(p) => p // p should be a pair of Int
                         ^
      <console>:21: error: not found: value p
                 case Foo(p) => p // p should be a pair of Int
                                ^
      

      The work-around to use the `FooHasType` implicit to force `T` to be `Any` in the case where it would otherwise be a tuple does not completely solve the issue because then the type info for the components of the tuple gets lost.

      So, the bottom line: it seems this bug makes it impossible to provide a representation-independent extractor for types such as `Try`.

        Activity

        Hide
        Adriaan Moors added a comment -

        More generally, even though we can write a case class with one tuple-valued argument,
        we can't express the corresponding extractor.

        This finally gives us a use case for Product1:

        scala> case class Holder[T](_1: T) extends Product1[T]
        defined class Holder
        
        scala> object Foo { def unapply(x: Any): Some[Holder[(Int, Int)]] = Some(Holder((1, 2))) }
        defined module Foo
        
        scala> 1 match { case Foo(t) => t }
        res0: Holder[(Int, Int)] = Holder((1,2))
        // expected (1, 2) as result type, by analogy to the Product2 extractor:
        
        scala> object Foo { def unapply(x: Any): Some[((Int, Int), Any)] = Some(((1, 2), "foo")) }
        defined module Foo
        
        scala> 1 match { case Foo(t, _) => t }
        res1: (Int, Int) = (1,2)
        

        The spec has to be changed, since now it says (specialized to the case for n = 1)

        > An unapply method in an object x matches the pattern x(p1) if it takes exactly one argument and unapply’s result type is Option[T], for some type T.
        > In this case, the (only) argument pattern p1 is typed in turn with expected type T.
        > The extractor pattern matches then all values v for which x.unapply(v) yields a value of form Some(v1), and p1 matches v1.

        Show
        Adriaan Moors added a comment - More generally, even though we can write a case class with one tuple-valued argument, we can't express the corresponding extractor. This finally gives us a use case for Product1: scala> case class Holder[T](_1: T) extends Product1[T] defined class Holder scala> object Foo { def unapply(x: Any): Some[Holder[(Int, Int)]] = Some(Holder((1, 2))) } defined module Foo scala> 1 match { case Foo(t) => t } res0: Holder[(Int, Int)] = Holder((1,2)) // expected (1, 2) as result type, by analogy to the Product2 extractor: scala> object Foo { def unapply(x: Any): Some[((Int, Int), Any)] = Some(((1, 2), "foo")) } defined module Foo scala> 1 match { case Foo(t, _) => t } res1: (Int, Int) = (1,2) The spec has to be changed, since now it says (specialized to the case for n = 1) > An unapply method in an object x matches the pattern x(p1) if it takes exactly one argument and unapply’s result type is Option [T] , for some type T. > In this case, the (only) argument pattern p1 is typed in turn with expected type T. > The extractor pattern matches then all values v for which x.unapply(v) yields a value of form Some(v1), and p1 matches v1.
        Hide
        Adriaan Moors added a comment -

        I'd propose the following spec change: allow Option[ProductN[...]] for any N >= 1, instead of N > 1
        keep the N = 1 and Option[T] as a fallback if T is not a product type

        Show
        Adriaan Moors added a comment - I'd propose the following spec change: allow Option[ProductN [...] ] for any N >= 1, instead of N > 1 keep the N = 1 and Option [T] as a fallback if T is not a product type
        Hide
        Adriaan Moors added a comment -

        I was wrong: the spec already covers this case. Correcting implementation in doTypedUnapply

        Show
        Adriaan Moors added a comment - I was wrong: the spec already covers this case. Correcting implementation in doTypedUnapply
        Show
        Adriaan Moors added a comment - https://github.com/scala/scala/pull/979

          People

          • Assignee:
            Adriaan Moors
            Reporter:
            Heather Miller
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development