17

Basically, I would like to be able to build a custom extractor without having to store it in a variable prior to using it.

This isn't a real example of how I would use it, it would more likely be used in the case of a regular expression or some other string pattern like construct, but hopefully it explains what I'm looking for:

def someExtractorBuilder(arg:Boolean) = new {
  def unapply(s:String):Option[String] = if(arg) Some(s) else None
}

//I would like to be able to use something like this 
val {someExtractorBuilder(true)}(result) = "test"
"test" match {case {someExtractorBuilder(true)}(result) => result }

//instead I would have to do this:
val customExtractor = someExtractorBuilder(true)
val customExtractor(result) = "test"
"test" match {case customExtractor(result) => result}

When just doing a single custom extractor it doesn't make much difference, but if you were building a large list of extractors for a case statement, it could make things more difficult to read by separating all of the extractors from their usage.

I expect that the answer is no you can't do this, but I thought I'd ask around first :D

Dan Shryock
  • 345
  • 2
  • 9
  • I did try it, I received a syntax error: scala> val {someExtractorBuilder(true)}(result) = "test" :1: error: illegal start of simple pattern val {someExtractorBuilder(true)}(result) = "test" ^ – Dan Shryock Mar 09 '10 at 19:43
  • What are you ultimately trying to do? There's probably a better way to do it than to make lots of extractors. – Jorge Ortiz Mar 10 '10 at 05:24
  • An example would be something like a date parsing routine that accepts multiple date formats, you could build multiple extractors one for each particular format. It would be nice if the date pattern could be near the extraction point so that you don't end up with odd variables like `yyyyMMddExtractor` and could instead put a `case {DateExtractor("yyyy-MM-dd")}(date) => date`. Definitely not a big deal, but it would have been nice :D – Dan Shryock Mar 10 '10 at 16:20

5 Answers5

8

Parameterising extractors would be cool, but we don't have the resources to implement them right now.

Adriaan Moors
  • 4,256
  • 1
  • 18
  • 10
4

Nope.

8.1.7 Extractor Patterns

An extractor pattern x (p 1 , . . . , p n ) where n ≥ 0 is of the same syntactic form as a constructor pattern. However, instead of a case class, the stable identifier x denotes an object which has a member method named unapply or unapplySeq that matches the pattern.

retronym
  • 54,768
  • 12
  • 155
  • 168
2

Late but there is a scalac plugin in one of my lib providing syntax ~(extractorWith(param), bindings):

x match {
  case ~(parametrizedExtractor(param)) => 
    "no binding"
  case ~(parametrizedExtractor(param), (a, b)) =>
    s"extracted bindings: $a, $b"
}

https://github.com/cchantep/acolyte/blob/master/scalac-plugin/readme.md

cchantep
  • 9,118
  • 3
  • 30
  • 41
2

One can customize extractors to certain extent using implicit parameters, like this:

object SomeExtractorBuilder {
  def unapply(s: String)(implicit arg: Boolean): Option[String] = if (arg) Some(s) else None
}

implicit val arg: Boolean = true
"x" match {
  case SomeExtractorBuilder(result) =>
    result
}

Unfortunately this cannot be used when you want to use different variants in one match, as all case statements are in the same scope. Still, it can be useful sometimes.

Suma
  • 33,181
  • 16
  • 123
  • 191
0

Though what you are asking isn't directly possible,
it is possible to create an extractor returning a contaner
that gets evaluated value in the if-part of the case evaluation. In the if part it
is possible to provide parameters.

object DateExtractor {
  def unapply(in: String): Option[DateExtractor] = Some(new DateExtractor(in));
}

class DateExtractor(input:String){
  var value:LocalDate=null;
  def apply():LocalDate = value;
  def apply(format: String):Boolean={
    val formater=DateTimeFormatter.ofPattern(format);
    try{
      val parsed=formater.parse(input, TemporalQueries.localDate());
      value=parsed
      true;
    } catch {
      case e:Throwable=>{
        false
      }
    }
  }
}

Usage:

object DateExtractorUsage{
  def main(args: Array[String]): Unit = {
    "2009-12-31" match {
      case DateExtractor(ext) if(ext("dd-MM-yyyy"))=>{
        println("Found dd-MM-yyyy date:"+ext())
      }
      case DateExtractor(ext) if(ext("yyyy-MM-dd"))=>{
        println("Found yyyy-MM-dd date:"+ext())
      }
      case _=>{
        println("Unable to parse date")
      }
    }
  }
}

This pattern preserves the PartialFunction nature of the piece of code.
I find this useful since I am quite a fan of the collect/collectFirst methods, which take a partial function as a parameter and typically does not leave room for precreating a set of extractors.

Espen Brekke
  • 404
  • 3
  • 7