11

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.DynamicVariables吗?

请注意,对于工厂方法,例如Something.getInstance,不可能使用常见的 Scala 模式

val something =
  new Something {
    x()
    y()
    z()
  }
4

6 回答 6

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)
于 2012-06-20T12:13:10.913 回答
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 那样紧凑),但它几乎与可能得到的规范一样接近。

(注意:如果您想最小化添加这些方法的运行时开销,您可以制作aaprivate val并拥有PipeAndTapextend AnyVal;那么这将是一个“值类”,只有在您需要传递对象时才会转换为真正的类; 仅仅调用一个方法实际上并不需要创建类。)

(第二点:在旧版本的 Scala 中,implicit class不存在。您必须单独编写implicit def将泛型转换为a的类和 an PipeAndTap。)

于 2012-06-20T13:36:17.733 回答
4

我认为,最接近的是在范围内导入此对象的成员:

val something = ...
import something._

x()
y()
z()

在这篇文章中,您可以找到另一个示例(在“关于理论基础的小更新”部分):

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


这种方法还有一个小优势 - 您可以导入单个成员并重命名它们:

import something.{x, y => doProcessing}
于 2012-06-20T12:01:22.123 回答
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)
于 2012-06-20T12:15:23.560 回答
0

好吧,我可以想到两种方法:将字符串作为参数传递,让宏更改字符串并编译它,或者简单地导入方法。如果 Scala 有无类型的宏,也许它们也可以使用——因为它没有它们,我不打算推​​测它。

无论如何,我将把宏观替代方案留给其他人。导入方法相当简单:

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

请注意,除了限制导入locally成员的范围外,它不会做任何事情。map

于 2012-06-20T16:50:50.410 回答
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))

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

于 2012-06-20T18:38:38.907 回答