Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`doto` for Scala

Tags:

scala

Clojure offers a macro called doto that takes its argument and a list of functions and essentially calls each function, prepending the (evaluated) argument:

(doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))
-> {a=1, b=2}

Is there some way to implement something similar in Scala? I envision something with the following form:

val something =
  doto(Something.getInstance) {
    x()
    y()
    z()
  }

which will be equivalent to

val something = Something.getInstance
something.x()
something.y()
something.z()

Might it be possible using scala.util.DynamicVariables?

Note that with factory methods, like Something.getInstance, it is not possible to use the common Scala pattern

val something =
  new Something {
    x()
    y()
    z()
  }
like image 927
Ralph Avatar asked Jun 20 '12 11:06

Ralph


4 Answers

I don't think there is such a thing built-in in the library but you can mimic it quite easily:

def doto[A](target: A)(calls: (A => A)*) =
  calls.foldLeft(target) {case (res, f) => f(res)}

Usage:

scala> doto(Map.empty[String, Int])(_ + ("a" -> 1), _ + ("b" ->2))
res0: Map[String,Int] = Map(a -> 1, b -> 2)

scala> doto(Map.empty[String, Int])(List(_ + ("a" -> 1), _ - "a", _ + ("b" -> 2)))
res10: Map[String,Int] = Map(b -> 2)

Of course, it works as long as your function returns the proper type. In your case, if the function has only side effects (which is not so "scalaish"), you can change doto and use foreach instead of foldLeft:

def doto[A](target: A)(calls: (A => Unit)*) =
  calls foreach {_(target)}

Usage:

scala> import collection.mutable.{Map => M}
import collection.mutable.{Map=>M}

scala> val x = M.empty[String, Int]
x: scala.collection.mutable.Map[String,Int] = Map()

scala> doto(x)(_ += ("a" -> 1), _ += ("a" -> 2))

scala> x
res16: scala.collection.mutable.Map[String,Int] = Map(a -> 2)
like image 173
Nicolas Avatar answered Oct 16 '22 05:10

Nicolas


In Scala, the "typical" way to do this would be to chain "tap" or "pipe" methods. These are not in the standard library, but are frequently defined as so:

implicit class PipeAndTap[A](a: A) {
  def |>[B](f: A => B): B = f(a)
  def tap[B](f: A => B): A = { f(a); a }
}

Then you would

(new java.util.HashMap[String,Int]) tap (_.put("a",1)) tap (_.put("b",2))

This is not as compact as the Clojure version (or as compact as Scala can be), but it is about as close to canonical as one is likely to get.

(Note: if you want to minimize run-time overhead for adding these methods, you can make a a private val and have PipeAndTap extend AnyVal; then this will be a "value class" which is only converted into a real class when you need an object to pass around; just calling a method doesn't actually require class creation.)

(Second note: in older versions of Scala, implicit class does not exist. You have to separately write the class and an implicit def that converts a generic a to a PipeAndTap.)

like image 40
Rex Kerr Avatar answered Oct 16 '22 05:10

Rex Kerr


I think, that the closest would be to import this object's members in scope:

val something = ...
import something._

x()
y()
z()

In this post you can find another example (in section "Small update about theoretical grounds"):

http://hacking-scala.posterous.com/side-effecting-without-braces


Also small advantage with this approach - you can import individual members and rename them:

import something.{x, y => doProcessing}
like image 36
tenshi Avatar answered Oct 16 '22 07:10

tenshi


More simple I guess:

val hm = Map [String, Int] () + ("a"-> 1) + ("b"-> 2) 

Your sample

val something =
  doto (Something.getInstance) {
    x()
    y()
    z()
  }

doesn't look very functional, because - what is the result? I assume you're side effecting.

Something.x().y().z()

could be a way if each call produces the type where the next function can act on.

z(y(x(Something)))

another kind of producing a result.

And there is the andThen method to chain method calls on collections, you might want to have a look at.

For your Map-example, a fold-left is another way to go:

val hm = Map [String, Int] () + ("a"-> 1) + ("b"-> 2) 

val l = List (("a", 8), ("b", 7), ("c", 9))
(hm /: l)(_ + _)
// res8: scala.collection.immutable.Map[String,Int] = Map(a -> 8, b -> 7, c -> 9)
like image 41
user unknown Avatar answered Oct 16 '22 07:10

user unknown