Clojure提供了一个名为doto
的宏,它接受其参数和函数列表,并基本上调用每个函数,在(计算的)参数前面加上:
(doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))
-> {a=1, b=2}
有没有办法在Scala中实现类似的东西?我设想了以下形式的东西:
val something =
doto(Something.getInstance) {
x()
y()
z()
}
将等同于
val something = Something.getInstance
something.x()
something.y()
something.z()
可能使用scala.util.DynamicVariable
s?
请注意,使用工厂方法(如Something.getInstance
)时,无法使用常见的Scala模式
val something =
new Something {
x()
y()
z()
}
答案 0 :(得分:12)
我不认为图书馆里有这样的内容,但你可以很容易地模仿它:
def doto[A](target: A)(calls: (A => A)*) =
calls.foldLeft(target) {case (res, f) => f(res)}
用法:
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)
当然,只要您的函数返回正确的类型,它就会起作用。在您的情况下,如果该函数只有副作用(不是那么“scalaish”),您可以更改doto
并使用foreach
代替foldLeft
:
def doto[A](target: A)(calls: (A => Unit)*) =
calls foreach {_(target)}
用法:
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)
答案 1 :(得分:5)
在Scala中,执行此操作的“典型”方法是链接“tap”或“pipe”方法。这些不在标准库中,但通常定义如下:
implicit class PipeAndTap[A](a: A) {
def |>[B](f: A => B): B = f(a)
def tap[B](f: A => B): A = { f(a); a }
}
然后你会
(new java.util.HashMap[String,Int]) tap (_.put("a",1)) tap (_.put("b",2))
这不像Clojure版本那样紧凑(或者像Scala那样紧凑),但它与人们可能获得的规范差不多。
(注意:如果您希望最大限度地减少添加这些方法的运行时间开销,可以a
为private val
并PipeAndTap
延长AnyVal
;那么这将是一个“值类”,当你需要一个对象传递时它只被转换成一个真正的类;只是调用一个方法实际上并不需要创建类。)
(第二个注释:在早期版本的Scala中,implicit class
不存在。您必须单独编写类,并将implicit def
转换为通用a
PipeAndTap
。)
答案 2 :(得分:4)
我认为,最接近的是在范围内导入此对象的成员:
val something = ...
import something._
x()
y()
z()
在这篇文章中你可以找到另一个例子(在“关于理论基础的小更新”一节中):
http://hacking-scala.posterous.com/side-effecting-without-braces
此方法也有小优势 - 您可以导入单个成员并重命名:
import something.{x, y => doProcessing}
答案 3 :(得分:1)
我想更简单:
val hm = Map [String, Int] () + ("a"-> 1) + ("b"-> 2)
您的样本
val something =
doto (Something.getInstance) {
x()
y()
z()
}
看起来不太实用,因为 - 结果是什么?我猜你是副作用。
Something.x().y().z()
如果每个调用产生下一个函数可以作用的类型,则可能是一种方式。
z(y(x(Something)))
另一种产生结果。
还有andThen
方法来对集合进行链接方法调用,您可能需要查看。
对于你的地图示例,折叠左边是另一种方式:
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)
答案 4 :(得分:0)
好吧,我可以想到两种方法:将字符串作为参数传递,让宏更改字符串并编译它,或者只是导入方法。如果Scala有无类型的宏,也许它们也可以被使用 - 因为它没有它们,我不打算推测它。
无论如何,我将把宏观替代品留给别人。导入方法非常简单:
val map = collection.mutable.Map[String, Int]()
locally {
import map._
put("a", 1)
put("b", 2)
}
请注意locally
除了限制导入map
成员的范围外,不会执行任何操作。
答案 5 :(得分:0)
链接多个动作的一个非常基本的方法是函数组合:
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))
在这种情况下,它不太实用,因为无法轻易推断出函数的类型......