6

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?

8
  • When I try to compile Map("" -> "") match { case Params.Foo(value) => 5 } with your object Params extends Dynamic the compiler melts down with a NullPointerException - definitely looks like a bug to me. Commented Jun 27, 2014 at 12:32
  • @wingedsubmariner as you can tell from the output of my Scala REPL, it's not the NullpointerException that is in my way. Commented Jun 27, 2014 at 13:18
  • I would not try to fight the language syntax here and would simply assign the extractor to a constant and use that constant in the match block; resorting to Dynamic here looks like a bad idea and in my opinion leads to unreadable non-Scalaesque code which Dynamic was not meant for. Commented Jun 27, 2014 at 22:39
  • @ErikAllik I'm using extractors like Params.Foo as nested objects all the time. I don't see how that is 'unreadable', and 'non-Scalaesque'. Unfiltered uses it all the time, also for extracting parameters, so I don't see why you shouldn't try to make the syntax a little less verbose and clunky. Also the ScalaDocs don't mention anything on what Dynamic was meant for, other than a pretty generic description that perfectly matches my use case. Commented Jun 28, 2014 at 7:43
  • 1
    Agreed. The ultimate solution is parameterised extractors, which - according to this should be in Scala 2.11, but I haven't found any reference to it yet in the release notes. Commented Jun 28, 2014 at 8:48

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.