4

Apparently unapply/unapplySeq in extractor objects do not support implicit parameters. Assuming here an interesting parameter a, and a disturbingly ubiquitous parameter b that would be nice to hide away, when extracting c.

[EDIT]: It appears something was broken in my intellij/scala-plugin installation that caused this. I cannot explain. I was having numerous strange problems with my intellij lately. After reinstalling, I can no longer reprodce my problem. Confirmed that unapply/unapplySeq do allow for implicit parameters! Thanks for your help.

This does not work (**EDIT:yes, it does):**

trait A; trait C; trait B { def getC(a: A): C }

def unapply(a:A)(implicit b:B):Option[C] = Option(b.getC(a))

In my understanding of what an ideal extractor should be like, in which the intention is intuitively clear also to Java folks, this limitation basically forbids extractor objects which depend on additional parameter(s).

How do you typically handle this limitation?

So far I've got those four possible solutions:

1) The simplest solution that I want to improve on: don't hide b, provide parameter b along with a, as normal parameter of unapply in form of a tuple:

object A1{ 
    def unapply(a:(A,B)):Option[C] = Option(a._2.getC(a._1)) }

in client code:

 val c1 = (a,b) match { case A1(c) => c1 }

I don't like it because there is more noise deviating that deconstruction of a into c is important here. Also since java folks, that have to be convinced to actually use this scala code, are confronted with one additional synthactic novelty (the tuple braces). They might get anti-scala aggressions "What's all this? ... Why then not use a normal method in the first place and check with if?".

2) define extractors within a class encapsulating the dependence on a particular B, import extractors of that instance. At import site a bit unusual for java folks, but at pattern match site b is hidden nicely and it is intuitively evident what happens. My favorite. Some disadvantage I missed?

class BDependent(b:B){ 
   object A2{ 
    def unapply(a:A):Option[C] = Option(b.getC(a))
         } }

usage in client code:

val bDeps = new BDependent(someB)
import bDeps.A2 
val a:A = ...
val c2 = a match { case A2(c) => c }
}

3) declare extractor objects in scope of client code. b is hidden, since it can use a "b" in local scope. Hampers code reuse, heavily pollutes client code (additionally, it has to be stated before code using it).

4) have unapply return Option of function B => C. This allows import and usage of an ubitious-parameter-dependent extractor, without providing b directly to the extractor, but instead to the result when used. Java folks maybe confused by usage of function values, b not hidden:

 object A4{
  def unapply[A,C](a:A):Option[B => C] = Option((_:B).getC(a))
   }

then in client code:

 val b:B = ...
 val soonAC: B => C = a match { case A4(x) => x }
 val d = soonAC(b).getD ...

Further remarks:

  • As suggested in this answer, "view bounds" may help to get extractors work with implicit conversions, but this doesn't help with implicit parameters. For some reason I prefer not to workaround with implicit conversions.
  • looked into "context bounds", but they seem to have the same limitation, don't they?
Community
  • 1
  • 1
ib84
  • 675
  • 5
  • 16

2 Answers2

4

In what sense does your first line of code not work? There's certainly no arbitrary prohibition on implicit parameter lists for extractor methods.

Consider the following setup (I'm using plain old classes instead of case classes to show that there's no extra magic happening here):

class A(val i: Int)
class C(val x: String)
class B(pre: String) { def getC(a: A) = new C(pre + a.i.toString) }

Now we define an implicit B value and create an extractor object with your unapply method:

implicit val b = new B("prefix: ")

object D {
  def unapply(a: A)(implicit b: B): Option[C] = Option(b getC a)
}

Which we can use like this:

scala> val D(c) = new A(42)
c: C = C@52394fb3

scala> c.x
res0: String = prefix: 42

Exactly as we'd expect. I don't see why you need a workaround here.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • Footnote: I know this is more of a comment than an answer, but a full working example wouldn't fit in a comment. I'm happy to delete if it doesn't answer the question. – Travis Brown Sep 09 '12 at 11:41
  • Hmm! That would be good news. But I'm confused... I've been fighting with this issue for several days. But I also had several issues with my intellij/scala plugin. Trying to reproduce my problem in the REPL... – ib84 Sep 09 '12 at 12:26
1

The problem you have is that implicit parameters are compile time (static) constraints, whereas pattern matching is a runtime (dynamic) approach.

trait A; trait C; trait B { def getC(a: A): C }

object Extractor {
  def unapply(a: A)(implicit b: B): Option[C] = Some(b.getC(a))
}

// compiles (implicit is statically provided)
def withImplicit(a: A)(implicit b: B) : Option[C] = a match { 
  case Extractor(c) => Some(c)
  case _            => None
}

// does not compile
def withoutImplicit(a: A) : Option[C] = a match { 
  case Extractor(c) => Some(c)
  case _            => None
}

So this is a conceptual problem, and the solution depends on what you actually want to achieve. If you want something along the lines of an optional implicit, you might use the following:

sealed trait FallbackNone {
  implicit object None extends Optional[Nothing] {
    def toOption = scala.None
  }
}
object Optional extends FallbackNone {
  implicit def some[A](implicit a: A) = Some(a)
  final case class Some[A](a: A) extends Optional[A] { 
    def toOption = scala.Some(a)
  }
}
sealed trait Optional[+A] { def toOption: Option[A]}

Then where you had implicit b: B you will have implicit b: Optional[B]:

object Extractor {
   def unapply(a:A)(implicit b: Optional[B]):Option[C] = 
      b.toOption.map(_.getC(a))
}

def test(a: A)(implicit b: Optional[B]) : Option[C] = a match { 
   case Extractor(c) => Some(c)
   case _            => None
}

And the following both compile:

test(new A {}) // None

{
  implicit object BImpl extends B { def getC(a: A) = new C {} }
  test(new A {}) // Some(...)
}
0__
  • 66,707
  • 21
  • 171
  • 266
  • In other words, it does not really have anything to do with the notion of extractors, but rather with the static nature of implicits. – 0__ Sep 09 '12 at 11:55
  • thank you for your answer. I don't quite understand your example / explanation: withoutImplicit() is not expected to compile simply because no implicit is given. Of course, I do provide one, but I no matches or strange ClassCastExceptions, for example that my main could not be cast to Nothing. Maybe this was also related to a broken intellij installation. Sorry, as of yet i cannot reproduce the error in the REPL... I'm curious, could you explain what you mean by optional implicit and what you are getting at the last three listsings? I don't understand, sorry... – ib84 Sep 09 '12 at 12:45
  • Well, if you do always have an implicit in your code visible when you want to use the extractor, than you wouldn't need the optional implicits, and the answer of Travis shows that. I had understood that you want to be able to compile a pattern match with the extractor, no matter whether such implicit is given or not. In that case, you can ask for the `Optional[B]` which is _always_ available (it falls back to `Optional.None` if no underlying implicit of type `B` is found). – 0__ Sep 09 '12 at 13:13
  • ah ok! now I understand your Optional. That could come in handy in other situations. Thanks – ib84 Sep 09 '12 at 13:17