`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))
В этом случае это не очень практично, так как тип функций не может быть легко выведен...