Scala的`doto`

时间:2012-06-20 11:49:28

标签: scala

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()
  }

6 个答案:

答案 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那样紧凑),但它与人们可能获得的规范差不多。

(注意:如果您希望最大限度地减少添加这些方法的运行时间开销,可以aprivate valPipeAndTap延长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))

在这种情况下,它不太实用,因为无法轻易推断出函数的类型......