Ralph
Ralph

Reputation: 32294

`doto` for 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()
  }

Upvotes: 11

Views: 481

Answers (6)

Rex Kerr
Rex Kerr

Reputation: 167901

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

Upvotes: 5

Landei
Landei

Reputation: 54584

One very basic way to chain several actions is function composition:

val f:Map[Int,String]=>Map[Int,String] = _ + (1 -> "x")
val g:Map[Int,String]=>Map[Int,String] = _ + (2 -> "y")
val h:Map[Int,String]=>Map[Int,String] = _ + (3 -> "z")

(h compose g compose f)(Map(42->"a"))
// Map[Int,String] = Map((42,a), (1,x), (2,y), (3,z))

In this case it's not very practical, though, as the type of the functions can't be inferred easily...

Upvotes: 0

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297255

Well, I can think of two ways of doing it: passing strings as parameters, and having a macro change the string and compile it, or simply importing the methods. If Scala had untyped macros, maybe they could be used as well -- since it doesn't have them, I'm not going to speculate on it.

At any rate, I'm going to leave macro alternatives to others. Importing the methods is rather simple:

val map = collection.mutable.Map[String, Int]()
locally {
  import map._
  put("a", 1)
  put("b", 2)
}

Note that locally doesn't do anything, except restrict the scope in which the members of map are imported.

Upvotes: 0

Nicolas
Nicolas

Reputation: 24769

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)

Upvotes: 12

user unknown
user unknown

Reputation: 36250

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)

Upvotes: 1

tenshi
tenshi

Reputation: 26576

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}

Upvotes: 4

Related Questions