Implement trait methods using subclasses' types - scala

I want a trait Foo to provide transform method that would apply a function to it. Also, I want to force implementing classes to have an increment method that would somehow transform the object as well. Naive solution:
trait Foo {
def transform(fun: Foo => Foo): Foo = fun(this)
def increment(n: Int): Foo
}
case class A(a: Int) extends Foo {
// expecting available: transform(fun: A => A): A
// to be implemented: increment(n: Int): A
...
}
Above won't work... Inherited transform still expects Foo => Foo, not A => A and increment still wants to return Foo, not A.
One more attempt:
trait Foo {
def transform[C <: Foo](fun: C => C): C = fun(this.asInstanceOf[C])
def increment[C <: Foo](n: Int): C
}
case class A(a: Int) extends Foo {
def increment(n: Int) = A(a + n)
}
A will not compile - it will still complain about the signature.
Taking out the increment function, transform works. However asInstanceOf looks a bit unsafe. Also, I need to explicitly provide type parameter to transform:
val a = A(1)
a.transform[A](x => x.copy(x.a + 1)) // returns A(2)
I wonder if there's a smart way to have it done.

The most direct way of getting what you want is to move the type parameter up to the trait declaration. That gives trait Foo[C]{...}. However, using copy in your transform still won't work, since the Foo trait doesn't know anything about anything that extends it. You can give it a bit more information with self typing:
trait Foo[C] {
this: C =>
def transform(fun: C => C): C = fun(this)
def increment(n: Int): C
}
case class A(a: Int) extends Foo[A] {
def increment(n: Int) = A(a + n)
}
The A extends Foo[A] is a little awkward to use here, but it works, since now when you extend Foo, it provides that type information back to the trait. That's still a little bit awkward though. It turns out there's a technique called type classes that we can use here to potentially improve things. First, you set up your trait. In a type class, there is exactly one implementation of the trait per type, so each method should also take in the instance that you want to operate on:
trait Foo[C] {
def transform(c: C)(f: C => C): C
def increment(c: C, inc: Int): C
}
Next, in the companion object you set up instances of the typeclass for the types you care about:
case class A(a: Int)
object Foo {
implicit val ATransform = new Foo[A] {
def transform (base: A)(f: A => A) = f(base)
def increment(base: A, inc: Int) = A(base.a+inc)
}
//Convenience function for finding the instance for a type.
//With this, Foo[A] is equivalent to implicitly[Foo[A]]
def apply[C](implicit foo: Foo[C]) = foo
}
Now we can use the type class as follows:
val b = A(3)
Foo[A].transform(b)(x=>x.copy(a=x.a+1)) //A(4)
Foo[A].increment(b,5) //A(8)

Related

What is the difference between MyClass and MyClass.this.type and how to turn one to another?

I have a situation similar to this:
trait Abst{
type T
def met1(p: T) = p.toString
def met2(p: T, f: Double=>this.type){
val v = f(1.0)
v.met1(p)
}
}
class MyClass(x: Double) extends Abst{
case class Param(a:Int)
type T = Param
val s = met2(Param(1), (d: Double) => new MyClass(d))
}
And it doesn't show errors until I run it, then it says:
type mismatch; found: MyClass, required:
MyClass.this.type
I tried also a solution with generic type but then I have conflict that this.T differs from v.T.
So I just need to overcome the error message above if possible?
Update
So, it turns out that this.type is the singleton type for that single instance. And I proposed in a comment usage of
val s = met2(Param(1), (d: Double) => (new MyClass(d)).asInstanceOf[this.type])
So just if someone would make a comment about this, I am aware how ugly it is, just interested how unsafe it is?
Also you all proposed to move definition of Param outside the class, which I definitely agree. So its definition will be in the companion object MyClass
The this.type is the singleton type that is inhabited by one single value, namely this. Therefore, accepting a function of type f: X => this.type as an argument is guaranteed to be nonsensical, because every invocation of f can just be replaced by this (plus the side-effects performed by f).
Here is a way of forcing your code to compile with minimal changes:
trait Abst { self =>
type T
def met1(p: T) = p.toString
def met2(p: T, f: Double => Abst { type T = self.T }){
val v = f(1.0)
v.met1(p)
}
}
case class Param(a:Int)
class MyClass(x: Double) extends Abst {
type T = Param
val s = met2(Param(1), (d: Double) => new MyClass(d))
}
But honestly: don't do it. And also don't do any of the F-bounded-stuff either, it will probably end up as a total mess, especially if you are not familiar with the pattern. Instead, refactor your code so that you don't have any self-referential spirals in it.
Update
Remark on why telling to the compiler that (new MyClass(d)) is of type this.type for some other this: MyClass is a really bad idea:
abstract class A {
type T
val x: T
val f: T => Unit
def blowup(a: A): Unit = a.asInstanceOf[this.type].f(x)
}
object A {
def apply[X](point: X, function: X => Unit): A = new A {
type T = X
val x = point
val f = function
}
}
val a = A("hello", (s: String) => println(s.size))
val b = A(42, (n: Int) => println(n + 58))
b.blowup(a)
This blows up with a ClassCastException, despite a and b both being of type A.
If you don't mind making the trait take T as a generic parameter, this is a fairly simple and straightforward equivalent solution:
trait Abst[T]{
def met1(p: T) = p.toString
def met2(p: T, f: Double=>Abst[T]){
val v = f(1.0)
v.met1(p)
}
}
case class Param(a:Int)
class MyClass(x: Double) extends Abst[Param]{
val s = met2(Param(1), (d: Double) => new MyClass(d))
}
I say it's equivalent because you're not losing any information by having met2 use the supertype instead of the subtype. The classic use case for referencing the subtype in a trait is e.g. having a method which you want to return MyClass instead of Abst even though it's defined in Abst, but that's not the situation you're in here. The only place your subtype reference is used is in the definition of f, and since function types are covariant on their output parameter you can pass any f: Double => MyClass into an f: Double => Abst[T] without problems.
If you do want to reference the subtype anyway, see Markus' answer... and if you do want to avoid having T be a generic parameter, things get a lot more complicated again, because now you have potential conflicts between the T of Abst versus the T of the subtype in the definition of met2.
To overcome this error message you have to use F-bounded polymorphism.
Your code will look somewhat like this:
trait Abst[F <: Abst[F, T], T]{ self: F =>
def met1(p: T): String = p.toString
def met2(p: T, f: Double => F): String = {
val v = f(1.0)
v.met1(p)
}
}
case class Param(a:Int)
class MyClass(x: Double) extends Abst[MyClass, Param] {
val s = met2(Param(1), (d: Double) => new MyClass(d))
}
Explanation:
Using self: F => inside of a trait or class definition constraints the value of this. So your code will not compile if this is not of type F.
We are using a cyclic type constraint of F: F <: Abst[F, T]. Though counterintuitive, the compiler does not mind it.
In the implementation, MyClass, we then extend MyClass with Abst[MyClass, Param], which in turn satisfies F <: Abst[F, T].
Now you can use F as return type of a function in Abst and have the MyClass return MyClass in the implementation.
You might think this solution is ugly, and if you do, then you're right.
Instead of using F-bounded polymorphism it's always recommended to use typeclasses for ad-hoc-polymorphism.
You will find more information about it in the link I provided earlier.
Really, read it. It will change your view on generic programming forever.
I hope this helps.

Scala GADT instance Functor

In Haskell I got:
data Foo a where
Bar :: a -> Foo a
Map :: (a -> b) -> Foo a -> Foo b
instance Functor Foo where
fmap = Map
In Scala I came up with:
import cats.Functor
trait Foo[A]
case class Bar[A](t: A) extends Foo[A]
case class Map[A,B](fun: A => B,foo: Foo[A]) extends Foo[B]
implicit val fooFunctor: Functor[Foo] = new Functor[Foo] {
def map[A,B](fa: Foo[A])(f: A => B) = Map(f,fa)
}
But Bar(40).map(_+2) gives me:
error: value map is not a member of Bar[Int]
I fairly new to Scala and don't know the way of inheritance that well.
What am I missing?
You need to upcast Bar(40) to Foo explicitly, and import the syntax implicits:
import cats.syntax.functor._
(Bar(40): Foo[Int]).map(_+2)
You need the upcast because Scala will infer the type Bar[Int] for the expression Bar(40) and this then interferes with finding the appropriate implicit that adds the map method. For this reason you sometimes see helper methods in the companion object to do the upcast for you:
object Foo {
def bar[A](a: A): Foo[A] = Bar(a)
// etc.
}
I thought a little bit about how one could solve this problem. Basically, we want to provide the functor operations even if only a super type is a functor. It seems to be impossible to express, that some higher-kinded type is the super type of an other, e.g. something like this is not possible: def bla[F[_], R[_] <: F]
We can however provide a conversion from R[] to F[] implicitly together with the functor for B:
abstract class ProvidesFor[R,F[_],FF[_[_]],A](val value: FF[F]) {
def convert(r: R): F[A]
}
implicit def providesFunctorForBar[A] =
new ProvidesFor[Bar[A],Foo,Functor,A](Functor[Foo]) {
override def convert(r: Bar[A]): Foo[A] = r
}
So the implict val will provide us with a functor for Foo and a convertion from Bar to Foo.
Now we can provide an implicit converstion to Functor.Ops like this:
implicit class FunctorOps[R[_],F[_],A](target: R[A])(implicit tc: ProvidesFor[R[A],F,Functor,A])
extends Functor.Ops[F,A] {
override val self = tc.convert(target)
override val typeClassInstance = tc.value
override def map[B](f: A => B): F[B] = typeClassInstance.map(self)(f)
override def imap[B](f: A => B)(g: B => A): F[B] = map(f)
}
Now this works as expected:
Bar(1).imap(_+2)(_+5)
We can also do the same for Map:
implicit def providesFunctorForMap[A,B] =
new ProvidesFor[Map[A,B],Foo,Functor,B](Functor[Foo]) {
override def convert(r: Map[A,B]): Foo[B] = r
}
Map((_:Int) + 1,Bar(5)).map(_+2)
For some strange reason I had to implement map and imap when extending Functor.Ops even though these methods are not abstract. In fact it compiles fine when I do not implement them but it fails at runtime with an AbstractMethodError. So somehow the compiler thinks the implementations are there but they are not. I suspect they are using some kind of byte code optimization tool that removes those implementations.

Generics re: Scala.math.Ordering wrapper instance for java.lang.Comparable

I want to make a simple Scala.math.Ordering wrapper instance for java.lang.Comparable. I would think this is standard, but I don't see anything similar in JavaConversions.
Can someone get something like this to work:
object ComparableOrdering[A <: Comparable[A]] extends Ordering[A] {
def compare(a: A, b: A): Int = a.compareTo(b)
}
Actually to handle classes like
interface A {}
class B implements A, Comparable<A> {}
it should be more like:
object ComparableOrdering[A <: Comparable[B] with B] extends Ordering[A] {
def compare(a: A, b: A): Int = a.compareTo(b)
}
Can anyone get this working?
The standard library already provides these instances via Ordering.ordered. For example, if you have a class like this:
class Foo(val i: Int) extends Comparable[Foo] {
def compareTo(that: Foo): Int = this.i - that.i
}
You'll automatically have an instance available:
scala> Ordering[Foo].lt(new Foo(0), new Foo(1))
res0: Boolean = true
The signature of Ordering.ordered isn't quite the same as yours:
implicit def ordered[A](implicit arg0: (A) => Comparable[A]): Ordering[A]
This works because the standard library provides implicit "conversion" functions that will up-cast any type to any of its supertypes.
If for some reason you wanted to define your own instances, you could do something like this:
implicit def comparableOrdering[A <: Comparable[A]]: Ordering[A] =
new Ordering[A] {
def compare(a: A, b: A): Int = {
println("Using custom instance for Comparable things")
a.compareTo(b)
}
}
And then:
scala> Ordering[Foo].lt(new Foo(0), new Foo(1))
Using custom instance for Comparable things
res0: Boolean = true
I'd guess that the standard library doesn't define these instances this way for reasons related to implicit prioritization, but the instances for Ordering are a mess and the question of why they use the A => Comparable[A] approach probably isn't worth thinking too hard about.

How can one provide manually specialized implementations with Scala specialization?

Specialization promises to provide high-efficiency implmentations for primitive types
with minimal extra boilerplate. But specialization seems to be too eager for its own good.
If I want to specialize a class or method,
def foo[#specialized(Byte) A](a: A): String = ???
class Bar[#specialized(Int) B] {
var b: B = ???
def baz: B = ???
}
then I am required to write a single implementation that covers both the specialized and the generic cases.
What if those cases are really different from each other, such that the implementations do not overlap?
For example, if I wanted to perform math on bytes, I would need to insert a bunch of & 0xFFs into the
logic.
I could possibly write a specialized type-class to do the math right, but doesn't that just push the same
problem back one level? How do I write my specialized + method for that type class in a way that doesn't
conflict with a more general implementation?
class Adder[#specialized(Byte) A] {
def +(a1: A, a2: A): A = ???
}
Also, once I do create a type-class this way, how do I make sure the correct type class is used for my specialized methods
instead of the general version (which, if it is truly general, should probably compile and certainly would run, except that it isn't what I want)?
Is there a way to do this without macros? Is it easier with macros?
This is my best attempt so far. It works but the implementation isn't pretty (even if the results are). Improvements are welcome!
There is a macro-free way to do this, both at the class and method level, and it does involve type classes--quite a lot of
them! And the answer is not exactly the same for classes and methods. So bear with me.
Manually Specialized Classes
You manually specialize classes the same way that you manually provide any kind of different implementation for classes:
your superclass is abstract (or is a trait), and the subclasses provide the implementation details.
abstract class Bippy[#specialized(Int) B] {
def b: B
def next: Bippy[B]
}
class BippyInt(initial: Int) extends Bippy[Int] {
private var myB: Int = initial
def b: Int = myB
def next = { myB += 1; this }
}
class BippyObject(initial: Object) extends Bippy[Object] {
private var myB: Object = initial
def b: B = myB
def next = { myB = myB.toString; this }
}
Now, if only we had a specialized method to pick out the right implementations, we'd be done:
object Bippy{
def apply[#specialized(Int) B](initial: B) = ??? // Now what?
}
So we've converted our problem of providing custom specialized classes and methods into just
needing to provide custom specialized methods.
Manually Specialized Methods
Manually specializing a method requires a way to write one implementation that can nonetheless
select which implementation you want (at compile time). Type classes are great at this. Suppose
we already had type classes that implemented all of our functionality, and that the compiler would
select the right one. Then we could just write
def foo[#specialized(Int) A: SpecializedFooImpl](a: A): String =
implicitly[SpecializedFooImpl[A]](a)
...or we could if implicitly was guaranteed to preserve specialization and if we only
ever wanted a single type parameter. In general these things are not true, so we'll write
our type class out as an implicit parameter rather than relying on the A: TC syntactic sugar.
def foo[#specialized(Int) A](a: A)(implicit impl: SpecializedFooImpl[A]): String =
impl(a)
(Actually, that's less boilerplate anyway.)
So we've converted our problem of providing custom specialized methods into just needing
to write specialized typeclasses and getting the compiler to fill in the correct ones.
Manually Specialized Type Classes
Type classes are just classes, and now we have to write specialized classes again, but
there's a critical difference. The user isn't the one asking for arbitrary instances.
This gives us just enough extra flexibility for it to work.
For foo, we need an Int version and a fully generic version.
trait SpecFooImpl[#specialized (Int), A] {
def apply(param: A): String
}
final class SpecFooImplAny[A] extends SpecFooImpl[A] {
def apply(param: A) = param.toString
}
final class SpecFooImplInt extends SpecFooImpl[Int] {
def apply(param: Int) = "!" * math.max(0, param)
}
Now we could create implicits to supply those type classes like so
implicit def specFooAsAny[A] = new SpecFooImplAny[A]
implicit val specFooAsInt = new SpecFooImplInt
except we have a problem: if we actually try to call foo: Int, both implicits will apply.
So if we just had a way to prioritize which type class we chose, we'd be all set.
Selection of type classes (and implicits in general)
One of the secret ingredients the compiler uses to determine the right implicit to use
is inheritance. If implicits come from A via B extends A, but B
declares its own that also could apply, those in B win if all else is equal.
So we put the ones we want to win deeper in the inheritance hierarchy.
Also, since you're free to define implicits in traits, you can mix them in anywhere.
So the last piece of our puzzle is to pop our type class implicits into a chain
of traits that extend each other, with the more generic ones appearing earlier.
trait LowPriorityFooSpecializers {
implicit def specializeFooAsAny[A] = new SpecializedFooImplAny[A]
}
trait FooSpecializers extends LowPriorityFooSpecializers {
implicit val specializeFooAsInt = new SpecializedFooImplInt
}
Mix in the highest-priority trait to wherever the implicits are needed, and the
type classes will be picked as desired.
Note that the type classes will be as specialized as you make them even if the
specialized annotation is not used. So you can do without specialized at all,
as long as you know the type precisely enough, unless you want to use specialized
functions or interoperate with other specialized classes. (And you probably do.)
A complete example
Let's suppose we want to make a two-parameter specialized bippy function that
will do apply the following transformation:
bippy(a, b) -> b
bippy(a, b: Int) -> b+1
bippy(a: Int, b) -> b
bippy(a: Int, b: Int) -> a+b
We should be able to achieve this with three type classes and a single specialized
method. Let's try, first the method:
def bippy[#specialized(Int) A, #specialized(Int) B](a: A, b: B)(implicit impl: SpecBippy[A, B]) =
impl(a, b)
Then the type classes:
trait SpecBippy[#specialized(Int) A, #specialized(Int) B] {
def apply(a: A, b: B): B
}
final class SpecBippyAny[A, B] extends SpecBippy[A, B] {
def apply(a: A, b: B) = b
}
final class SpecBippyAnyInt[A] extends SpecBippy[A, Int] {
def apply(a: A, b: Int) = b + 1
}
final class SpecBippyIntInt extends SpecBippy[Int, Int] {
def apply(a: Int, b: Int) = a + b
}
Then the implicits in chained traits:
trait LowerPriorityBippySpeccer {
// Trick to avoid allocation since generic case is erased anyway!
private val mySpecBippyAny = new SpecBippyAny[AnyRef, AnyRef]
implicit def specBippyAny[A, B] = mySpecBippyAny.asInstanceOf[SpecBippyAny[A, B]]
}
trait LowPriorityBippySpeccer extends LowerPriorityBippySpeccer {
private val mySpecBippyAnyInt = new SpecBippyAnyInt[AnyRef]
implicit def specBippyAnyInt[A] = mySpecBippyAnyInt.asInstanceOf[SpecBippyAnyInt[A]]
}
// Make this last one an object so we can import the contents
object BippySpeccer extends LowPriorityBippySpeccer {
implicit val specBippyIntInt = new SpecBippyIntInt
}
and finally we'll try it out (after pasting everything in together in :paste in the REPL):
scala> import Speccer._
import Speccer._
scala> bippy(Some(true), "cod")
res0: String = cod
scala> bippy(1, "salmon")
res1: String = salmon
scala> bippy(None, 3)
res2: Int = 4
scala> bippy(4, 5)
res3: Int = 9
It works--our custom implementations are enabled. Just to check that we can use
any type, but we don't leak into the wrong implementation:
scala> bippy(4, 5: Short)
res4: Short = 5
scala> bippy(4, 5: Double)
res5: Double = 5.0
scala> bippy(3: Byte, 2)
res6: Int = 3
And finally, to verify that we have actually avoided boxing, we'll time bippy at
summing a bunch of integers:
scala> val th = new ichi.bench.Thyme
th: ichi.bench.Thyme = ichi.bench.Thyme#1130520d
scala> val adder = (i: Int, j: Int) => i + j
adder: (Int, Int) => Int = <function2>
scala> var a = Array.fill(1024)(util.Random.nextInt)
a: Array[Int] = Array(-698116967, 2090538085, -266092213, ...
scala> th.pbenchOff(){
var i, s = 0
while (i < 1024) { s = adder(a(i), s); i += 1 }
s
}{
var i, s = 0
while (i < 1024) { s = bippy(a(i), s); i += 1 }
s
}
Benchmark comparison (in 1.026 s)
Not significantly different (p ~= 0.2795)
Time ratio: 0.99424 95% CI 0.98375 - 1.00473 (n=30)
First 330.7 ns 95% CI 328.2 ns - 333.1 ns
Second 328.8 ns 95% CI 326.3 ns - 331.2 ns
So we can see that our specialized bippy-adder achieves the same kind of performance
as specialized Function2 does (about 3 adds per ns, which is about right for a modern
machine).
Summary
To write custom specialized code using the #specialized annotation,
Make the specialized class abstract and manually supply concrete implementations
Make specialized methods (including generators for a specialized class) take typeclasses that do the real work
Make the base typeclass trait #specialized and provide concrete implementations
Provide implicit vals or defs in an inheritance-hierarchy of traits so the correct one is selected
It's a lot of boilerplate, but at the end of it all you get a seamless custom-specialized experience.
This is an answer from the scala internals mailing list:
With miniboxing specialization, you can use the reflection feature:
import MbReflection._
import MbReflection.SimpleType._
import MbReflection.SimpleConv._
object Test {
def bippy[#miniboxed A, #miniboxed B](a: A, b: B): B =
(reifiedType[A], reifiedType[B]) match {
case (`int`, `int`) => (a.as[Int] + b.as[Int]).as[B]
case ( _ , `int`) => (b.as[Int] + 1).as[B]
case (`int`, _ ) => b
case ( _ , _ ) => b
}
def main(args: Array[String]): Unit = {
def x = 1.0
assert(bippy(3,4) == 7)
assert(bippy(x,4) == 5)
assert(bippy(3,x) == x)
assert(bippy(x,x) == x)
}
}
This way, you can choose the exact behavior of the bippy method based on the type arguments without defining any implicit classes.
I know it's quite old, but I bumped at it looking for something else and maybe it'll prove useful. I had a similar motivation, and answered it in how to check I'm inside a specialized function or class
I used a reverse lookup table - SpecializedKey is a specialized class which equals all other instances with the same specialization, so I can perform a check like this
def onlyBytes[#specialized E](arg :E) :Option[E] =
if (specializationFor[E]==specializationFor[Byte]) Some(arg)
else None
Of course, there's no performance benefit when working with individual primitive values, but with collections, especially iterators, it becomes useful.
final val AllButUnit = new Specializable.Group((Byte, Short, Int, Long, Char, Float, Double, Boolean, AnyRef))
def specializationFor[#specialized(AllButUnit) E] :ResolvedSpecialization[E] =
Specializations(new SpecializedKey[E]).asInstanceOf[ResolvedSpecialization[E]]
private val Specializations = Seq(
resolve[Byte],
resolve[Short],
resolve[Int],
resolve[Long],
resolve[Char],
resolve[Float],
resolve[Double],
resolve[Boolean],
resolve[Unit],
resolve[AnyRef]
).map(
spec => spec.key -> spec :(SpecializedKey[_], ResolvedSpecialization[_])
).toMap.withDefaultValue(resolve[AnyRef])
private def resolve[#specialized(AllButUnit) E :ClassTag] :ResolvedSpecialization[E] =
new ResolvedSpecialization[E](new SpecializedKey[E], new Array[E](0))
class ResolvedSpecialization[#specialized(AllButUnit) E] private[SpecializedCompanion]
(val array :Array[E], val elementType :Class[E], val classTag :ClassTag[E], private[SpecializedCompanion] val key :SpecializedKey[E]) {
private[SpecializedCompanion] def this(key :SpecializedKey[E], array :Array[E]) =
this(array, array.getClass.getComponentType.asInstanceOf[Class[E]], ClassTag(array.getClass.getComponentType.asInstanceOf[Class[E]]), key)
override def toString = s"#specialized($elementType)"
override def equals(that :Any) = that match {
case r :ResolvedSpecialization[_] => r.elementType==elementType
case _ => false
}
override def hashCode = elementType.hashCode
}
private class SpecializedKey[#specialized(AllButUnit) E] {
override def equals(that :Any) = that.getClass==getClass
override def hashCode = getClass.hashCode
def className = getClass.getName
override def toString = className.substring(className.indexOf("$")+1)
}

Implicit parameters won't work on unapply. How to hide ubiquitous parameters from extractors?

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?
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.
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(...)
}