0

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 :)

Peter Neyens
  • 9,770
  • 27
  • 33
Dr_Benway
  • 61
  • 5

1 Answers1

1

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:

0__
  • 66,707
  • 21
  • 171
  • 266
  • Thanks for answering :). But isn't this kinda overcomplicating it over using a mutable.HashMap? And still relying on mutability, as well... Also, I don't know how many times I will need to add elements to it so I should have a method handle all of this. Following your advice, that what I could do: var recipes =new immutable.HashMap[String, Any]; Inside my method: recipes += ("string" -> 2). Am I right? – Dr_Benway Nov 02 '15 at 21:32
  • It depends on how you want to isolate your state. You can write an entire program with immutable state to the top level. Using immutable state can make your program easier to reason about. For example you can decide on concurrency from the use-site, whereas if you bake mutability into your objects you'll have trouble getting your stuff run correctly under concurrency… – 0__ Nov 02 '15 at 21:35
  • Also try not to use concrete implementations like `collection.immutable.HashMap`. Use the general type instead, i.e. `collection.immtuable.Map` which is aliased for convenience to just `Map`. – 0__ Nov 02 '15 at 21:37
  • So would this work? http://pastebin.com/as9v1pUt I'm still using the + operator to yield a new structure and the latter is reassigned to the var each time...do you think it's a good solution? And why is the generic Map better? – Dr_Benway Nov 02 '15 at 21:39
  • Actually that's not good....I get an exception when I call it with recipes.get(key).asInstanceOf[CrucibleRecipe] because I can't cast scala.Some – Dr_Benway Nov 02 '15 at 21:46
  • You should never cast anything, that's a clear indicator you are doing something wrong. The `get` method of a `Map` gives you an `Option[A]` not an `A`, because we want to avoid `null` in Scala. See this tutorial: http://danielwestheide.com/blog/2012/12/19/the-neophytes-guide-to-scala-part-5-the-option-type.html – 0__ Nov 02 '15 at 22:58
  • Right, I forgot about that...sorry but I have just a week of experience... using apply instead of get solves my problem :) It might not be the cleanest way, but I'm relying on a Java API, so some stuff is not on my side... – Dr_Benway Nov 02 '15 at 23:22