Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic Extractors in Scala

Tags:

scala

One of the things I don't like about extractors is that they cannot have parameters. So I cannot have extractors like Param in:

req match { case Param("foo")(foo) => … }

Dynamic Extractors?

That's unfortunate, and I hope it will change some day, but then this morning I figured I could fix it by using the Dynamic trait.

object Params extends Dynamic {
  def selectDynamic(name: String) = new {
    def unapply(params: Map[String, String]): Option[String] = params.get(name)
  }
}

… hoping that would allow me use Params in a pattern matching statement like this:

req match { case Params.Foo(value) => 
  // matching Map("Foo" -> "Bar"), extracting "Bar" in value

It doesn't work

… but it doesn't work. It seems the compiler is still getting confused.

scala> Map("Foo" -> "bar") match { case Params.Foo(value) => value }
<console>:10: error: value applyDynamic is not a member of object Params
error after rewriting to Params.<applyDynamic: error>("Foo")
possible cause: maybe a wrong Dynamic method signature?
              Map("Foo" -> "bar") match { case Params.Foo(value) => value }
                                               ^
<console>:10: error: not found: value value
              Map("Foo" -> "bar") match { case Params.Foo(value) => value }
                                                                    ^

Which I find surprising, since

object Params {
  object Foo {
    def unapply{params: Map[String, String]): Option[String] = … 
  }
}

would work fine. Also, if I assign Params.Foo to a variable first, everything is okay:

scala> val Foo = Params.Foo
Foo: AnyRef{def unapply(params: Map[String,String]): Option[String]} = Params$$anon$1@f2106d8

scala> Map("Foo" -> "bar") match { case Foo(value) => value }
warning: there were 1 feature warning(s); re-run with -feature for details
res2: String = bar

Should this be considered a bug?

like image 374
Wilfred Springer Avatar asked Jun 27 '14 12:06

Wilfred Springer


People also ask

What are Scala extractors?

In Scala Extractor is defined as an object which has a method named unapply as one of its part. This method extracts an object and returns back the attributes. This method is also used in Pattern matching and Partial functions.

What is the difference between Unapply and apply when would you use them?

Whereas the apply method is like a constructor which takes arguments and creates an object, the unapply takes an object and tries to give back the arguments. This is most often used in pattern matching and partial functions. The apply method creates a CustomerID string from a name .

What is pattern matching in Scala?

Pattern matching is a mechanism for checking a value against a pattern. A successful match can also deconstruct a value into its constituent parts. It is a more powerful version of the switch statement in Java and it can likewise be used in place of a series of if/else statements.


1 Answers

The canonical answer is Can extractors be customized with parameters in the body of a case statement (or anywhere else that an extractor would be used)?

but the hacking blog suggests the trick of passing arguments as arbitrary names to dynamic selection, as tried in the question.

$ scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_60).
Type in expressions for evaluation. Or try :help.

scala> class X(pattern: String) { val RegExp = new { def unapplySeq(s: String) = pattern.r.unapplySeq(s) } }
defined class X

scala> import language._
import language._

scala> case object p extends Dynamic { def selectDynamic(pattern: String) = new X(pattern) }
defined object p

scala> "abcdef" match { case p.`.*(b.*d).*`.RegExp(s) => s }
res0: String = bcd

The extra selection is required because of a crashing bug in 2.11 which is different from the 2.10 error shown in the question:

scala> class RegExp(pattern: String) { def unapplySeq(s: String) = pattern.r.unapplySeq(s) }
defined class RegExp

scala> case object p extends Dynamic { def selectDynamic(pattern: String) = new RegExp(pattern) }
defined object p

scala> "abcdef" match { case p.`.*(b.*d).*`(s) => s }
java.lang.NullPointerException
    at scala.tools.nsc.typechecker.PatternTypers$PatternTyper$class.inPlaceAdHocOverloadingResolution(PatternTypers.scala:68)

The working example in 2.10:

$ scala210 -language:_
Welcome to Scala version 2.10.5 (OpenJDK 64-Bit Server VM, Java 1.7.0_95).
Type in expressions to have them evaluated.
Type :help for more information.

scala> :pa
// Entering paste mode (ctrl-D to finish)

class X(key: String) { val get = new { def unapply(params: Map[String, String]): Option[String] = params.get(key) }}
object Params extends Dynamic {
  def selectDynamic(name: String) = new X(name)
}

// Exiting paste mode, now interpreting.

defined class X
defined module Params

scala> Map("Foo" -> "bar") match { case Params.Foo.get(value) => value }
res0: String = bar

This is similar to what is shown at the end of the question, but makes it obvious that dynamic selection can be used this way.

like image 199
som-snytt Avatar answered Nov 07 '22 14:11

som-snytt