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?

Wilfred Springer
  • 10,869
  • 4
  • 55
  • 69
  • 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. – wingedsubmariner Jun 27 '14 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. – Wilfred Springer Jun 27 '14 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. – Erik Kaplun Jun 27 '14 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](http://unfiltered.databinder.net/Within+the+Parameters.html), so I don't see why you shouldn't try to make the syntax a little less verbose and clunky. Also the [ScalaDocs](http://www.scala-lang.org/api/current/index.html#scala.Dynamic) don't mention anything on what `Dynamic` was meant for, other than a pretty generic description that perfectly matches my use case. – Wilfred Springer Jun 28 '14 at 07:43
  • IMO, it makes it look as if `Foo` was an actual static attribute of `Params`, which it's not; it's just a workaround way of passing dynamic string values to a function. In other words, it's unreadable/un-scalaesque for the same reason Scala is a statically typed language with a highly expressive type system and not a dynamically typed language such as Groovy, Python or PHP. But in the end, it's a matter of opinion of course. – Erik Kaplun Jun 28 '14 at 08:33
  • 1
    Agreed. The ultimate solution is parameterised extractors, which - according to [this](https://issues.scala-lang.org/browse/SI-5435) should be in Scala 2.11, but I haven't found any reference to it yet in the release notes. – Wilfred Springer Jun 28 '14 at 08:48
  • On another note, I think if-else is still a usable tool and pattern matching in Scala has grown to be a little bit overused :) – Erik Kaplun Jul 02 '14 at 09:18
  • `if` that is sensible `then` I would use it `else` I would use pattern matching. – Wilfred Springer Jul 03 '14 at 09:47

1 Answers1

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.

Community
  • 1
  • 1
som-snytt
  • 39,429
  • 2
  • 47
  • 129