`doto` для 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?

Обратите внимание, что с помощью методов factory, таких как Something.getInstance, невозможно использовать общий шаблон Scala

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

Ответ 1

Я не думаю, что в библиотеке есть такая вещь, но вы можете легко имитировать ее:

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)

Ответ 2

В 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 a private val и иметь PipeAndTap extend AnyVal, тогда это будет "значение класс", который только преобразуется в реальный класс, когда вам нужен объект для прохождения, просто вызов метода на самом деле не требует создания класса.)

(Второе примечание: в старых версиях Scala, implicit class не существует. Вы должны отдельно написать класс и implicit def, который преобразует общий a в PipeAndTap. )

Ответ 3

Я думаю, что ближайшим было бы импортировать этот объект в область видимости:

val something = ...
import something._

x()
y()
z()

В этом сообщении вы можете найти другой пример (в разделе "Небольшое обновление о теоретических основаниях" ):

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


Также небольшое преимущество в этом подходе - вы можете импортировать отдельные элементы и переименовывать их:

import something.{x, y => doProcessing}

Ответ 4

Проще, я думаю:

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)

Ответ 5

Ну, я могу подумать о двух способах этого: передать строки как параметры, а макрос изменить строку и скомпилировать ее, или просто импортировать методы. Если у Scala есть нетипизированные макросы, возможно, они тоже могут быть использованы - поскольку у них их нет, я не буду размышлять над ним.

Во всяком случае, я собираюсь оставить макро альтернативы другим. Импорт методов довольно прост:

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

Обратите внимание, что locally ничего не делает, за исключением ограничения области, в которой импортируются члены map.

Ответ 6

Один очень простой способ связать несколько действий - это композиция функции:

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

В этом случае это не очень практично, так как тип функций не может быть легко выведен...