I am new to Scala, how to extends one properties value from other object. Like in angular, angular.copy(obj1, obj2);
If I have two objects, obj1, obj2, both having the same property names. obj2's value would be overridden to obj1.
How to achieve this in Scala?
Unlike JavaScript, Scala does not allow you to add new properties to existing objects. Objects are created with a specific type and that type defines the properties on the object. If your objects are implemented as a case class then it is easy to make a copy of an object:
val obj = obj1.copy()
You can also update some of the fields of obj1 during the copy and leave others untouched:
val obj = obj1.copy(count=obj2.count)
This will create a new object with all the same values as obj1 except for the field count which will take the value from obj2.
It has been noted in the comments that in Scala you will typically create new objects from old ones, rather than modifying existing objects. This may seem cumbersome at first but it make it much easier to understand the behaviour of more complex code because you know that objects won't change under your feet.
Note on object in Scala vs JS
Note that object in Scala as a completely different concept from the usage in Javascript.
Scala deliberately is not using Prototype base inheritance.
So in short that this is not possible is a feature not a mssing capability of the language.
see https://docs.scala-lang.org/tour/singleton-objects.html
Solution : Use a Map to simulate protoype base inheritance/composition
You can use a Map to achieve a similar behaviour:
scala> val sayHello = () => "Hello"
sayHello: () => String = <function0>
scala> val obj1 = Map("sayHello" -> sayHello , "id" -> 99)
obj1: scala.collection.immutable.Map[String,Any] = Map(sayHello -> <function0>, id -> 99)
scala> val obj2 = Map("id" -> 1)
obj2: scala.collection.immutable.Map[String,Int] = Map(id -> 1)
scala> obj1 ++ obj2
res0: scala.collection.immutable.Map[String,Any] = Map(sayHello -> <function0>, id -> 1)
Related
I am new to Scala and was wondering what is the difference between initializing a Map data structure using the following three ways:
private val currentFiles: HashMap[String, Long] = new HashMap[String, Long]()
private val currentJars = new HashMap[String, Long]
private val currentVars = Map[String, Long]
There are two different parts to your question.
first, the difference between using an explicit type or not (cases 1 and 2) goes for any class, not necessarily containers.
val x = 1
Here the type is not explicit, and the compiler will try to figure it out using type inference. The type of x will be Int.
val x: Int = 1
Same as above, but now explicitly. If whatever you have at the right of = can't be cast to an Int, you will get a compiler error.
val x: Any = 1
Here we will still store a 1, but the type of the variable will be a parent class, using polymorphism.
The second part of your question is about initialization. The base initialization is as in java:
val x = new List[Int]()
This calls the class constructor and returns a new instance of the exact class.
Now, there is a special method called .apply that you can define and call with just parenthesis, like this:
val x = Seq[Int]()
This is a shortcut for this:
val x = Seq.apply[Int]()
Notice this is a function on the Seq object. The return type is whatever the function wants it to be, it is just another function. That said, it is mostly used to return a new instance of the given type, but there are no guarantees, you need to look at the function documentation to be sure of the contract.
That said, in the case of val x = Map[String, Long]() the implementation returns an actual instance of immutable.HashMap[String, Long], which is kind of the default Map implementation.
Map and HashMap are almost equivalent, but not exactly the same thing.
Map is trait, and HashMap is a class. Although under the hood they may be the same thing (scala.collection.immutable.HashMap) (more on that later).
When using
private val currentVars = Map[String, Long]()
You get a Map instance. In scala, () is a sugar, under the hood you are actually calling the apply() method of the object Map. This would be equivalent to:
private val currentVars = Map.apply[String, Long]()
Using
private val currentJars = new HashMap[String, Long]()
You get a HashMap instance.
In the third statement:
private val currentJars: HashMap[String, Long] = new HashMap[String, Long]()
You are just not relying anymore on type inference. This is exactly the same as the second statement:
private val currentJars: HashMap[String, Long] = new HashMap[String, Long]()
private val currentJars = new HashMap[String, Long]() // same thing
When / Which I use / Why
About type inference, I would recommend you to go with type inference. IMHO in this case it removes verbosity from the code where it is not really needed. But if you really miss like-java code, then include the type :) .
Now, about the two constructors...
Map vs HashMap
Short answer
You should probably always go with Map(): it is shorter, already imported and returns a trait (like a java interface). This last reason is nice because when passing this Map around you won't rely on implementation details since Map is just an interface of what you want or need.
On the other side, HashMap is an implementation.
Long answer
Map is not always a HashMap.
As seen in Programming in Scala, Map.apply[K, V]() can return a different class depending on how many key-value pairs you pass to it (ref):
Number of elements Implementation
0 scala.collection.immutable.EmptyMap
1 scala.collection.immutable.Map1
2 scala.collection.immutable.Map2
3 scala.collection.immutable.Map3
4 scala.collection.immutable.Map4
5 or more scala.collection.immutable.HashMap
When you have less then 5 elements you get an special class for each of these small collections and when you have an empty Map, you get a singleton object.
This is done mostly to get better performance.
You can try it out in repl:
import scala.collection.immutable.HashMap
val m2 = Map(1 -> 1, 2 -> 2)
m2.isInstanceOf[HashMap[Int, Int]]
// false
val m5 = Map(1 -> 1, 2 -> 2, 3 -> 3, 4 -> 4, 5 -> 5, 6 -> 6)
m5.isInstanceOf[HashMap[Int, Int]]
// true
If you are really curious you can even take a look at the source code.
So, even for performance you should also probably stick with Map().
I'm in the process of learning Scala, and I have to say that I'm really enjoying it.
I'm porting to Scala a previous Java project of mine, trying to take advantage of the language when I can/know how to (mainly pattern matching and trying to use vals, not into recursion or functions yet).
In the java project, I had an HashMap that started as empty and would get filled by using some methods. Then, of course, I'd use hmap.get(key) to retrieve the objects.
I'd like to use an immutable HashMap this time, but I'm kind of struggling with it! Here's my code:
val recipes = new HashMap[String,Any]()
private def addCrucibleRecipe(tag: String, tagAddon: String, result: ItemStack, cat: Any, aspects: AspectList) {
val recipe = new CrucibleRecipe(some params)
if(recipe.isInstanceOf[CrucibleRecipe]) recipes + (tag -> recipe)
else throw new IllegalArgumentException(tag + " is not a valid recipe!")
}
As you can see, I'm using the + operator, which should return a new HashMap with the previous values and the new one in its tail.
When I call recipes.get(key), however, the list contains Option None.
This means i'm still pointing the old, empty HashMap.
How the hell can I update the reference? And maybe that may sound dumb but I'm really new to functional programming. How is it better than a mutable one?
And if there's an even more functional or efficient way to do this in Scala please tell me, I'm eager to learn :)
Yes, the + method will return a new updated map instance. But you keep a reference here to a mutable value recipes. Unfortunately, that mutable map inherits the methods meant for immutable maps, like +, and so you create a new (mutable but different) map and "throw it away". This is what I mean:
import scala.collection.mutable
val m0 = mutable.Map.empty[String, Int]
m0.put("hello", 1)
val m1 = m0 + ("world" -> 2)
m0.keys // Set(hello) -- this is the "old" map still!
m1.keys // Set(world, hello) -- this is the newly created map
So the correct approach would be to use an immutable map and a var to store it:
var m2 = Map.empty[String, Int] // immutable map
m2 = m2 + ("hello" -> 1)
m2 += ("world" -> 2) // were m2 += ... is short for m2 = m2 + ...
Of course now you have moved the mutable state into having a variable m2 (or var recipes in your example). So you have to define the "breaking point" where you want to store state in your program. For example you could move it one level up:
case class Recipe(ingredients: String*)
case class Recipes(map: Map[String, Recipe]) {
def add(name: String, recipe: Recipe): Recipes =
new Recipes(map + (name -> recipe))
}
val r0 = Recipes(Map.empty)
val r1 = r0.add("pancakes", Recipe("milk", "eggs", "farine"))
Note that in your example addCrucibleRecipe does not have a return value, so you seem to intend to overwrite some state in your containing object. In the case-class example here, I am returning an entirely new copy of that object instead.
You may want to look at these questions:
val-mutable versus var-immutable in Scala
Scala "update" immutable object best practices
I have a special Map that will eventually do some consistency checking of values, which are restricted to have special meaning. For now I just want to create a Schema that acts exactly like a Map[String, Any], in particular I'd like to instantiate is with a list of mappings, and not force the types for the Map to be specified, so they are always [String, Any]. So instead of
val myMap:Map[String,Any] = Map("one" -> 1, "two" -> "2", ...)
I'd like to be able to have:
val mySchema:Schema = Schema("one" -> 1, "two" -> "2", ...)
Map is a trait so I think I need to extend a class like HashMap
class Schema extends HashMap[String, Any]
when I instantiate it with a list of initial mappings I get
val mySchema = new Schema("one" -> 1, "two" -> "2", ...)
Error:(110, 19) too many arguments for constructor Schema: ()drivers.Schema
val mySchema = new Schema("one" -> 1, "two" -> "2")
^
There is some magic inside HashMap that is far beyond me to read (it extends 1 class with 5 traits). But it looks like the constructor's "contents" (a list of mappings?) are passed to something's initWithContents(contents) pseudo constructor. Do I need something like that there?
class Schema(elems: Tuple2[String, Any]*) extends HashMap[String, Any] {
this ++= elems
}
val mySchema = new Schema("one" -> 1, "two" -> "2")
Explanation:
-> is syntactic sugar for a tuple, so the constructor type is a variable number of tuples
The "normal" way of constructing a Map/HashMap calls the apply method in the companion object which is implemented in GenMapFactory.scala.
Unfortunately, this does not work for an immutable HashMap. As far as I can tell, the only way to "extend" an immutable HashMap is by creating a class that holds an internal reference to one, as described e.g. in this answer to a similar question on SO.
You'll notice that the syntax you're using to create the Schema instance is slightly different than your target syntax as well as the standard Map syntax:
Schema("one" -> 1, "two" -> "2", ...)
new Schema("one" -> 1, "two" -> "2", ...)
Map("one" -> 1, "two" -> "2", ...)
In particular, there is a new in the second case. When you create a Map without new, you are invoking a function called apply on a companion module of the same name. In this case, the function which accepts a variable length sequence of arguments is defined the Map object's inherited GenMapFactory type and it has this signature:
def apply[A, B](elems: (A, B)*): CC[A, B] = (newBuilder[A, B] ++= elems).result
The magic derives the from Scala compiler resolving this method, because methods in the companion module are part of its implicit scope. You will need to do something similar in by adding a Schema object:
object Schema {
def apply(entries: (String, Any)*) = new Schema() ++ entries
}
If you define the object, this will work:
Schema("one" -> 1, "two" -> "2") // scala.collection.immutable.Map[String,Any](one -> 1, two -> 2)
One downside of this approach is that you lose type specificity after construction, but if you're just performing consistency checking on creation this may be enough. If you want the Schema type to be returned on invocation of the apply function or returned by other methods calls implemented in super types and enclosing scope, you will probably need to integrate with the Scala 2.8 collections API as documented here.
In Mercury I can use:
A = B^some_field := SomeValue
to bind A to a copy of B, except that some_field is SomeValue instead of whatever it was in B. I believe the Haskell equivalent is something like:
a = b { some_field = some_value }
Does Scala have something like this for "modifying" immutable values. The alternative seems to be to have a constructor that directly sets every field in the instance, which isn't always ideal (if there are invarients the constructor should be maintaining). Plus it would be really clunky and much more fragile if I had to explicitly pass every other value in the instance I want to have a modified copy of.
I couldn't find anything about this by googling, or in a brief survey of the language reference manual or "Scala By Example" (which I have read start-to-finish, but haven't absorbed all of yet, so it may well be in there).
I can see that this feature could have some weird interactions with Java-style access protection and subclasses though...
If you define your class as a case class, a convenient copy method is generated, and calling it you can specify with named parameters new values for certain fields.
scala> case class Sample(str: String, int: Int)
defined class Sample
scala> val s = Sample("text", 42)
s: Sample = Sample(text,42)
scala> val s2 = s.copy(str = "newText")
s2: Sample = Sample(newText,42)
It even works with polymorphic case classes:
scala> case class Sample[T](t: T, int: Int)
defined class Sample
scala> val s = Sample("text", 42)
s: Sample[java.lang.String] = Sample(text,42)
scala> val s2 = s.copy(t = List(1,2,3), 42)
s2: Sample[List[Int]] = Sample(List(1, 2, 3),42)
Note that s2 has a different type than s.
You can use case classes for this, but you don't have to. Case classes are nothing magical - the modifier case just saves you a lot of typing.
The copy method is realized by the use of named and default parameters. The names are the same as the fields and the defaults are the current values of the fields. Here's an example:
class ClassWithCopy(val field1:String, val field2:Int) {
def copy(field1:String = this.field1, field2:Int = this.field2) = {
new ClassWithCopy(field1,field2);
}
}
You can use this just like the copy method on case classes. Named and default parameters are a very useful feature, and not only for copy methods.
If the object you're planning on modifying is a case class then you can use the autogenerated copy method:
scala> val user = User(2, "Sen")
user: User = User(2,Sen)
scala> val corrected = user.copy(name = "Sean")
corrected: User = User(2,Sean)
I have an instance of a collection that I want to store externally and then restore back into the original collection type. For example
class Foo {
var x : List[Int]
}
val f = new Foo
f.x = List(1, 2, 3)
I "serialize" out f, I want to reflectively create a new Foo, f2, and populate f2.x with the correct results.
I can create the new Foo by doing classOf[Foo].newInstance, but how do I then create the correct collection type and populate it?
Note, I'm making lots of assumptions here, notable:
1) I know the type of f.x, and I can even serialize out the type of that
2) I'm serializing out the contents of x into something that preserves the values
3) I don't want to use any "standard" serialization
I've tried to use the builders available on the original collection, but I don't quite understand how that works enough to pull it off.
Thanks,
Dave
It would be easier to help here if we had a better idea of the problem you're trying to solve, e.g. why you don't want to use the standard object serialization.
That said, if you really do want to do reflection over Scala collection classes, you'll probably need to know a few things about how Scala currently compiles its classes and objects:
If you want the class for the List object (not for the List class), the name is scala.collection.immutable.List$ - note the final dollar sign.
If you want the singleton List object instance, that's stored as the field MODULE$.
Most scala collection companion objects provide a newBuilder method which creates an object that has a += ($plus$eq) method and a result method that allow you to create a new collection.
So you could do something like:
scala> def buildByReflection[T](collectionClassName: String, items: Array[T]) = {
| val companionClass = Class.forName(collectionClassName + "$")
| val companion = companionClass.getField("MODULE$").get(null)
| val newBuilder = companionClass.getMethod("newBuilder")
| val builder = newBuilder.invoke(companion)
| val plusEq = builder.getClass.getMethod("$plus$eq", classOf[Object])
| for (item <- items) {
| plusEq.invoke(builder, item.asInstanceOf[AnyRef])
| }
| builder.getClass.getMethod("result").invoke(builder)
| }
buildByReflection: [T](collectionClassName: String,items: Array[T])java.lang.Object
scala> buildByReflection("scala.collection.immutable.List", Array(1, 2, 3))
res0: java.lang.Object = List(1, 2, 3)