I've seen some blogs on the Pimp my Library pattern, and these seem to work well for adding behavior to classes.
But what if I have a case class and I want to add data members to it? As a case class I can't extend it (inheriting from a case class is deprecated/strongly discouraged). Will any of these pimp patterns allow me to add data to a case class?
No - I don't see how you could make this work because the enriched instance is usually thrown away (note: newly the pimp-my-library pattern is called enrich-my-library). For example:
scala> case class X(i: Int, s: String)
defined class X
scala> implicit class Y(x: X) {
| var f: Float = 0F
| }
defined class Y
scala> X(1, "a")
res17: X = X(1,a)
scala> res17.f = 5F
res17.f: Float = 0.0
scala> res17.f
res18: Float = 0.0
You would have to make sure you kept hold of the wrapped instance:
scala> res17: Y
res19: Y = Y#4c2d27de
scala> res19.f = 4
res19.f: Float = 4.0
scala> res19.f
res20: Float = 4.0
However, I find this not useful in practice. You have a wrapper; you're better off making this explicit
This is not the way to do. Just a proof of possibility. With this way you can get plenty of problems.
scala> :paste
// Entering paste mode (ctrl-D to finish)
case class A(i: Int)
class B(a: A){
var s: String = ""
}
object B{
val as = scala.collection.mutable.WeakHashMap[A, B]()
}
implicit def a2b(a: A) = B.as.getOrElseUpdate(a, new B(a))
// Exiting paste mode, now interpreting.
defined class A
defined class B
defined module B
a2b: (a: A)B
scala> val a = A(1)
a: A = A(1)
scala> a.s = "test"
scala> a.s
res0: String = test
WeakHashMap: A hash map with references to entries which are weakly reachable. Entries are removed from this map when the key is no longer (strongly) referenced. This class wraps java.util.WeakHashMap.
Note that due to case class's overridden equals method you get this funny behavior:
scala> A(2).s = "test2"
scala> A(2).s
res2: String = test2
so you should not use case class or use it with override def equals(that: Any) = this eq that.asInstanceOf[AnyRef].
Related
Say I define a trait and case class that extends it:
trait A {
var list: List[Int] = List()
def add(i: Int) = list = i :: list
}
case class B(str: String) extends A
If I then create an instance of B, modify the list and make a copy of it, the copy takes the values of the list as defined in trait A:
val b = B("foo")
b.add(3)
println("B: " + b.list)
println("Copied: " + b.copy(str = "bar").list)
Output:
B: List(3)
Copied: List()
I know this is probably not good functional programming practice anyway, but is there a way I can force the copy of B to take the altered version of the list for instance b?
You can accomplish this by declaring the list in the case class, and satisfy your trait's type by declaring a method. So:
trait A {
def list: List[Int]
}
case class B(i: Int, var list: List[Int]) extends A
And then your example:
scala> val b = B(2, List(1))
b: B = B(2,List(1))
scala> b.list = List(3)
b.list: List[Int] = List(3)
scala> println("B: " + b.list)
B: List(3)
scala> println("Copied: " + b.copy(i = 4).list)
Copied: List(3)
I don't know what you're doing that requires a mutable list in your class, but I imagine that even though this satisfies your example it might not satisfy whatever problem you're working on since:
scala> val a: A = b
a: A = B(2,List(3))
scala> a.list
res2: List[Int] = List(3)
scala> a.list = List(4)
<console>:15: error: value list_= is not a member of A
a.list = List(4)
When trying to view an instance of B as an A you won't be able to assign the list type. If you were dealing with an instance of A you'd have to do something like
scala> a match {
| case itsAB: B => itsAB.list = List(4); itsAB
| case other => other
| }
res3: A = B(2,List(4))
But either way, this is how you'd do it. Either that or you can follow phongnt's advice to create a non-case class and define a copy method yourself.
In a comment you said:
I failed to mention that the trait attribute needs to be modifiable with a reference to the trait itself
But this sounds like global mutable state to me. If you really need that, and I encourage you to not to then you can use an object:
object A {
#volatile
var list = List.empty[Int]
}
But I doubt this gets at what you're expressing in your question either since there is not an individual list still per instance here. So... looking at your updated question, I can only guess that something like this is what you're trying to accomplish:
object A {
#volatile
var list = List.empty[Int]
}
trait A {
def list = A.list
def add(i: Int) = A.list = i :: A.list
}
case class B(str: String) extends A
Which lets you:
scala> val b = B("hi")
b: B = B(hi)
scala> b.add(0)
scala> b.add(1)
scala> A.list
res4: List[Int] = List(1, 0)
But again, this is global mutable state, it's not a good thing. It's not threadsafe. So if there's a way for you to figure out your problem without doing this... I'd encourage you to do so.
The copy method is generated for you by the compiler when you declare a case class and it is only parameterised with the variables you define in the case class
Hence, you can definitely have copy method do that for you by declaring the variable list in the case class e.g. case class B(i: Int, list: List[Int]). You will also need override val modifier to satisfy scalac. However, scalac doesn't allow you to override a mutable variable, so you need to change the trait too, for example
trait A {
val list: List[Int] = List(1)
}
case class B(i: Int, override val list: List[Int]) extends A
vars in a scala class automatically get getters & setters you can see through scala reflection via members
import scala.reflect.runtime.{universe => ru}
class A(var x: Int)
scala> ru.typeOf[A].members.filter{_.name.toString.contains("x")}
res22: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(variable x, method x_=, method x)
However, if you create a subclass, which re-uses the var name in the constructor, the getter is gone:
class B(x:Int, var y: Int) extends A(x)
scala> ru.typeOf[B].members.filter{_.name.toString.contains("x")}
res23: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(value x, method x_=)
scala> res23.head.asTerm.isVal
res25: Boolean = true
This seems a little misleading ... after all, B still does have a getter for x (and its not a val)
scala> val b = new B(5,6)
b: B = B#270288ed
scala> b.x
res26: Int = 5
scala> b.x = 7
b.x: Int = 7
scala> b.x
res27: Int = 7
If I try to pretend that the value x I got from members is a getter, I get an error:
scala> val xGetter = res23.head.asTerm
xGetter: reflect.runtime.universe.TermSymbol = value x
scala> val objMirror = ru.runtimeMirror(getClass.getClassLoader).reflect(b)
objMirror: reflect.runtime.universe.InstanceMirror = instance mirror for B#270288ed
scala> val getterMirror = objMirror.reflectField(xGetter)
scala.ScalaReflectionException: Scala field x isn't represented as a Java field, neither it has a Java accessor method
note that private parameters of class constructors don't get mapped onto fields and/or accessors,
unless they are used outside of their declaring constructors.
What is the right workaround here? Is it completely wrong to have a subclass name its constructor args the same as the names in the parent args? Or instead of calling members, do I need to work my up all super-classes to get all getters & setters?
Note that members gives me the inherited getter as long as the subclass doesn't create a constructor w/ the same name:
class Y(var x: Int)
class Z(q:Int, z: Int) extends Y(q)
scala> ru.typeOf[Z].members.filter{_.name.toString.contains("x")}
res28: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(method x_=, method x)
EDIT
In case its unclear, I'm really asking:
1) is this a bug in scala reflection?
2) if not, should I:
(a) never have classes use names of constructor fields be the same as the name of fields in base classes? (if so, I'm probably defining all my classes wrong ...)
or
(b) to get all getters & setters, should I just go through the list of all parent classes and use declarations, rather than relying on members to do the right thing, since it doesn't work in this one case?
EDIT 2
in response to #som-snytt's answer, the visible methods of x are really on x in A, not the param in the constructor to B. Eg.:
class A(var x: Int){def showMeX {println(x)}}
class B(x:Int, var y: Int) extends A(x)
scala> val b = new B(5,10)
scala> b.showMeX
5
scala> b.x = 17
b.x: Int = 17
scala> b.showMeX
17
so I don't really think that the either the getter or setter for x has been shadowed, from the perspective of normal user code. Its only been shadowed for reflection code ... and it doesn't make sense to me that there would be two different versions of shadowing.
2) if not, should I: (a) never have classes use names of constructor fields be the same as the name of fields in base classes?
Since they wouldn't let me fix this, that's exactly what I do. I try to give all constructor parameters new names distinct from all inherited names. Here's a typical example within the compiler.
class PackageClassSymbol protected[Symbols] (owner0: Symbol, pos0: Position, name0: TypeName)
Yes, it's ridiculous.
https://groups.google.com/forum/#!topic/scala-language/9jLsT_RRQR0
https://issues.scala-lang.org/browse/SI-3194
https://issues.scala-lang.org/browse/SI-4762
https://issues.scala-lang.org/browse/SI-6880
Oh boy, don't keep pulling that thread...
https://issues.scala-lang.org/browse/SI-7475
https://issues.scala-lang.org/browse/SI-2568
https://issues.scala-lang.org/browse/SI-6794
It shouldn't be hard to see that the odds of any of it being addressed are nil. It's a perfect example of why I quit.
By the way, if you use -Xlint it warns you about this. That's mentioned in SI-4762.
% cat a.scala
class A(var x: Int)
class B(x:Int, var y: Int) extends A(x) {
def z = x
}
% scalac -Xlint a.scala
a.scala:3: warning: private[this] value x in class B shadows mutable x inherited from class A.
Changes to x will not be visible within class B - you may want to give them distinct names.
def z = x
^
one warning found
Well, in:
class B(x:Int, var y: Int) extends A(x)
The x in B is private (not a case class, no val or var specifier), the x in A is public (you specified var). I'm not too familiar with this reflection API, but does it show private members?
If you instead:
scala> class B(val x:Int, var y: Int) extends A(10)
<console>:9: error: overriding variable x in class A of type Int;
value x needs `override' modifier
class B(val x:Int, var y: Int) extends A(10)
^
The only public x is the one in A, which can be shown here:
scala> class B(x:Int, var y: Int) extends A(10)
defined class B
scala> new B(2,3).x
res4: Int = 10
If you want to override the parent member, use override, and change the parent to something that can be overriden.
I guess as long as the answer to my question is not (2a), then if anybody else runs into this, here is a workaround. Maybe it will be unnecessary in the future depending on the answer to (1).
(Some extra stuff here, but maybe useful also)
import scala.reflect.runtime.{universe => ru}
object ReflectionUtils {
def extractGetterSetterPairs(typ: ru.Type): Seq[GetterSetterPair] = {
typ.baseClasses.foldLeft(Seq[GetterSetterPair]()){case (acc, clsSymb) =>
extractGetterSetterPairs(clsSymb.asClass.toType, acc)
}
}
private def extractGetterSetterPairs(typ: ru.Type, acc: Seq[GetterSetterPair]): Seq[GetterSetterPair] = {
val terms = typ.declarations.collect{case x if x.isTerm => x.asTerm}
acc ++ terms.filter{x => x.isGetter}.map{x => x -> x.setter}.
filter{case(g,s) => s.isTerm}.map{case(g,s) =>
GetterSetterPair(g,s.asTerm)
}
}
def termName(t: ru.TermSymbol): String = {
t.name.toString.trim
}
}
case class GetterSetterPair(getter: ru.TermSymbol, setter: ru.TermSymbol) {
val name = ReflectionUtils.termName(getter)
val fieldType = {
//this is way more complicated than it should be. But
// 1) getters for some reason are not instances of ru.MethodType
// java.lang.ClassCastException: scala.reflect.internal.Types$NullaryMethodType cannot be cast to scala.reflect.api.Types$MethodTypeApi
// 2) its a headache to get the types out of setters
val m = setter.typeSignature.
asInstanceOf[ru.MethodType]
m.params.head.typeSignature
}
}
How would I write use a variable as a type? For example:
scala> class A
defined class A
scala> class B extends A
defined class B
scala> def convert[F, T](from: F): T = from.asInstanceOf[T]
convert: [F, T](from: F)T
scala> val b = new B
b: B = B#54038f36
scala> convert[B, A](b)
res18: A = B#54038f36
This works fine. But I am interested in doing:
scala> val aType = classOf[A]
aType: Class[A] = class A
scala> convert[B, aType](b)
<console>:12: error: not found: type aType
convert[B, aType](b)
How would I do this without initializing an object and using obj.type in there? I suspect that I need to use some reflection features (maybe TypeTag?), but I couldn't find out what the correct thing to use here.
Thanks!
Java's Class has a method cast which does exactly what you need:
val aType = classOf[A]
aType.cast(b)
Now as a method:
def convert[F,T](from: F, clazz: Class[T]) = clazz.cast(from)
And call:
convert(b, aType)
How can a parameter's default value reference another parameter? If it cannot, how to work around that?
case class A(val x:Int, val y:Int = x*2)
Error (reasonably enough):
scala> case class B(val x:Int, val y:Int = x*2)
<console>:7: error: not found: value x
case class B(val x:Int, val y:Int = x*2)
^
This requires that you use multiple parameter lists:
case class A(x: Int)(y: Int = x*2)
Default values can only refer to parameters in preceding lists.
Be careful however with case classes, because their equality only takes into the account the first parameter list, therefore:
A(1)() == A(1)(3) // --> true!!
Since you asked for the work-around, if it's not obvious how to preserve caseness:
scala> :pa
// Entering paste mode (ctrl-D to finish)
case class Foo(x: Int, y: Int)
object Foo {
def apply(x: Int): Foo = apply(x, 2*x)
}
// Exiting paste mode, now interpreting.
defined class Foo
defined object Foo
scala> Foo(5,6)
res45: Foo = Foo(5,6)
scala> Foo(5)
res46: Foo = Foo(5,10)
I know this has been discussed on SO in other posts before and I understand the basic difference between the use of def and val. def is used for defining a method and val for an immutable reference. What I am trying to accomplish by asking this question is to understand if there is something more to def. Can it be used interchangeably with a val?
Recently I tried the following code and cannot convince myself if my present understanding of def is sufficient:
scala> def i: Int = 3
i: Int
scala> i
res2: Int = 3
So I am curious, is this equivalent to val i = 3?
Then I tried this:
scala> i()
<console>:9: error: Int does not take parameters
i()
I did this just to test my understanding of the semantics of def. Now I want to know, when i is a method, why Scala complains with "...does not take parameters"?
Next I tried the following:
scala> def i(): Int = 3
i: ()Int
scala> i()
res4: Int = 3
This time Scala seems to agree that i is a method. So can I use def in place of val interchangeable to declare and initialize a variable?
Both
def i = 3
and
def i() = 3
declare methods. The only difference is, that the first one is a method without a parameter list and the second is a method with an empty parameter list. The former is usually used for methods without side effects and the latter for methods with side effects.
You should use a val instead of a def if the value never changes and you want to avoid recomputing it. A def gets recomputed every time it is called, while a val is assigned a value only once.
def defines a method, val defines an immutable value, as you already know.
One major difference is in when the expression on the right side of the = is evaluated. For a method, it is evaluated each time you call the method. For a value, it is evaluated when you initialize the value. See the difference:
scala> def i: Int = { println("Hello"); 3 }
i: Int
scala> i
Hello
res0: Int = 3
scala> i
Hello
res1: Int = 3
scala> val i: Int = { println("Hello"); 3 }
Hello
i: Int = 3
scala> i
res2: Int = 3
Just to add on the top of the Kim's answer, you can override def by val.
// Entering paste mode (Ctrl+D to finish)
trait A {
def i: Int
def num: Long
}
class B extends A {
val i = 7
val num = 20L
}
// Exiting paste mode, now interpreting.
defined trait A
defined class B
scala> val b = new B
b: B = B#2d62bdd8
scala> b.i
res1: Int = 7
scala> b.num
res2: Long = 20