Kah
Kah

Reputation: 51

Lazy evaluation in a Map

I'm trying to make a function that executes other functions (which return Strings) depending on an input.
For example:

def execute(input: String) = {
    val actions = Map("foo" -> someObject.doStuff(), "bar" -> someObject.doThing())
    actions.get(input) }

Is there a way to make the functions in the Map to be called only if the corresponding key exists?

Upvotes: 4

Views: 1989

Answers (3)

Bogdan Vakulenko
Bogdan Vakulenko

Reputation: 3390

object someObject {
  def doStuff() = "foo"
  def doThing() = "bar"

}

//if you need a string as a return value.
//there will be empty string for missing keys
def execute(input: String):String = {
  val actions = Map( "foo" -> someObject.doStuff _ ,
                     "bar" -> someObject.doThing _ )

  actions.getOrElse(input,()=>"")()
}


//if you need an Option[String] as a return value.
//there will be None value for missing keys
def executeOption(input: String):Option[String] = {
  val actions = Map( "foo" -> someObject.doStuff _ ,
                     "bar" -> someObject.doThing _ )

  actions.get(input).map(_())
}

Upvotes: 4

ygor
ygor

Reputation: 1756

Yes, there is a way how to execute object methods inside a Map. But you first have to put functions into the map. What you did, was putting results of object methods into your map. So your methods were executed and their results were put as values into the map.

Let's assume, that your methods are defined in this class:

class SomeObject {
  def doStuff(): String = "I did stuff"
  def doThing(): String = "I did a thing"
}

And you have an instance of the class:

val someObject = new SomeObject

First way, how to delay execution of these methods is to create new function using lambdas and execute methods inside these lambdas:

val actions = Map(
  "foo" -> (() => someObject.doStuff()),
  "bar" -> (() => someObject.doThing()),
)

You can use special syntax to convert object methods directly into lambdas:

val actions = Map(
  "foo" -> someObject.doStuff _,
  "bar" -> someObject.doThing _
)

If you specify type of the map explicitly, than you can get rid of the underscore too:

val actions: Map[String, () => String] = Map(
  "foo" -> someObject.doStuff,
  "bar" -> someObject.doThing
)

Next, you need to get the function from the map. If there is no function for the key, than use a default function - one, that always returns None

val action = actions.getOrElse(input, () => None)

And finally, you invoke the action:

action()

Upvotes: 1

sarveshseri
sarveshseri

Reputation: 13985

From what I can tell, you are not really looking for lazy map. You are trying to build some kind of execution engine, which does something based on string input.

Now, the simple trick to achieve this is to have a actual functions in your Map and execute the corresponding function for the input string.

So, lets say you have an object A,

object A {
  var i = 1

  def doFirstThing(): Unit = {
    i = i + 1
    println(s"first thing :: $i")
  }

  def doSecondThing(): Unit = {
    i = i - 1
    println(s"second thing :: $i")
  }
}

And you want to build an executor for executing doFirstThing for input "first" and doSecondThing for input "second".

So, now you need to create functions f1 and f2 which will execute A.doFirstThing() and A.doSecondThing().

Its pretty simple actually,

scala> val f1 = () => A.doFirstThing()
// f1: () => Unit = $$Lambda$1235/1187572055@7e192338

scala> val f2 = () => A.doSecondThing()
// f2: () => Unit = $$Lambda$1236/1696761182@47df3efe

Or, you can use _ to convert def (methods) to functions,

scala> val f1 = A.doFirstThing _
// f1: () => Unit = $$Lambda$1317/1501701470@7306836f

scala> val f2 = A.doSecondThing _
// f2: () => Unit = $$Lambda$1318/545144829@2a3251ad

Now, you can define your map using these,

scala> def execute(input: String): Unit = {
     |   val map = Map(
     |     "first" -> f1,
     |     "second" -> f2
     |   )
     | 
     |   map.get(input).foreach(f => f())
     | }
// execute: (input: String)Unit

scala> execute("first")
// first thing :: 2

scala> execute("second")
// second thing :: 1

scala> execute("second")
// second thing :: 0

scala> execute("first")
// first thing :: 1

scala> execute("first")
// first thing :: 2

And it will plainly ignore all other inputs,

scala> execute("abc")

Upvotes: 1

Related Questions