typelevel / scala

Typelevel Scala, a fork of Scala
http://typelevel.org/scala/
372 stars 21 forks source link

Generalise the return type of extractors from Option[A] to TraversableOnce[A] #93

Closed stacycurl closed 8 years ago

stacycurl commented 9 years ago

Ever 3 months I attempt to write an extractor like this:

val acceptHeaders = List("text/html", "application/json")

acceptHeaders match {
  case Accepts("application/json") => ...
}

At which point I remember that it's impossible but that's only the case because extractors have to return an Option (I'm ignoring the Boolean variant here), what if that were generalized.

Here's code for a normal extractor along with the decompiled java that's produced.

object StandardExtractor {
  class Person(val pet: Option[Pet])
  object Person { def unapply(person: Person): Option[Pet] = person.pet }

  class Pet(val flea: Option[Flea])
  object Pet { def unapply(pet: Pet): Option[Flea] = pet.flea }

  class Flea(val name: String)
  object Flea { def unapply(flea: Flea): Option[String] = Some(flea.name) }

  val person = new Person(Some(new Pet(Some(new Flea("itchy")))))

  def usage(): Unit = {
    val hasItchyFlea = person match {
      case Person(Pet(Flea("itchy"))) => true
      case _ => false
    }
  }

  /*
    public void usage()
    {
        boolean flag;
label0:
        {
            StandardExtractor.Person person = StandardExtractor.person();
            StandardExtractor.Person person1 = person;
            Option option = StandardExtractor.Person..MODULE$.unapply(person1);
            if(!option.isEmpty())
            {
                StandardExtractor.Pet pet = (StandardExtractor.Pet)option.get();
                Option option1 = StandardExtractor.Pet..MODULE$.unapply(pet);
                if(!option1.isEmpty())
                {
                    StandardExtractor.Flea flea = (StandardExtractor.Flea)option1.get();
                    Option option2 = StandardExtractor.Flea..MODULE$.unapply(flea);
                    if(!option2.isEmpty())
                    {
                        String s = (String)option2.get();
                        if("itchy".equals(s))
                        {
                            flag = true;
                            break label0;
                        }
                    }
                }
            }
            flag = false;
        }
        boolean hasItchyFlea = flag;
    }
  */
}

Here's code for a generalised extractor that returns a List instead of option along with imagined compiled code to support that.

object BacktrackingExtractor {
  class Person(val pets: List[Pet])
  object Person { def unapply(person: Person): List[Pet] = person.pets }

  class Pet(val fleas: List[Flea])
  object Pet { def unapply(pet: Pet): List[Flea] = pet.fleas }

  class Flea(val name: String)
  object Flea { def unapply(flea: Flea): Option[String] = Some(flea.name) }

  val person = new Person(List(
    new Pet(Nil), 
    new Pet(List(new Flea("itchy"), new Flea("scratchy")))
  ))

  def imaginedUsage(): Unit = {
    val hasItchyFlea = person match {
      case Person(Pet(Flea("itchy"))) => true
      case _ => false
    }
  }

  /*
    public void imagedUsage()
    {
        BacktrackingExtractor.Person person = BacktrackingExtractor.person();

        boolean flag;

        label0:
        {
            for (BacktrackingExtractor.Pet pet:
                JavaConversions.asJavaIterable(BacktrackingExtractor.Person$.MODULE$.unapply(person))) {

                for (BacktrackingExtractor.Flea flea:
                    JavaConversions.asJavaIterable(BacktrackingExtractor.Pet$.MODULE$.unapply(pet))) {

                    Option option2 = BacktrackingExtractor.Flea$.MODULE$.unapply(flea);

                    if(!option2.isEmpty())
                    {
                        String s = (String)option2.get();
                        if("itchy".equals(s))
                        {
                            flag = true;
                            break label0;
                        }
                    }
                }
            }
            flag = false;
        }
        boolean hasItchyFlea = flag;
    }
   */
}
milessabin commented 9 years ago

Doesn't unapplySeq help here?

stacycurl commented 9 years ago

No (but I did panic for a bit, I've never actually used unapplySeq)

The following code prints false, false, true, indicating that unapplySeq can be used to match the entire contents of a list, but not a single element.

object Contains extends App {
  def unapplySeq[T](x: List[T]): Option[List[T]] = Some(x)

  println(List(1, 2, 3) match {
    case Contains(2) => true
    case _ => false
  })

  println(List(1, 2, 3) match {
    case Contains(4) => true
    case _ => false
  })

  println(List(1, 2, 3) match {
    case Contains(1, 2, 3) => true
    case _ => false
  })
}
milessabin commented 8 years ago

To resurrect this issue, please rework it as an issue/PR against Lightbend Scala (ie. scala/scala).