Reputation: 32294
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.DynamicVariable
s?
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
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
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
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
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
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
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