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) => … }
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
… 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?
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.
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 .
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With