В данный момент Я пытаюсь понять функциональное программирование в Scala, и я столкнулся с проблемой, которую сам не могу понять.
Представьте себе следующую ситуацию:
У вас есть два класса: Controller и Bot . Bot - это независимый Actor, который инициируется контроллером , выполняет дорогостоящую операцию и возвращает результат контроллеру . Поэтому цель контроллера легко описать: создать несколько объектов бота , запустить их и получить результат.
Пока все хорошо; Я могу реализовать все это без использования изменяемых объектов.
Но что мне делать, если мне нужно сохранить результат, который возвращает Bot , использовать его позже в качестве входных данных для другого бота (и позже означает, что я не знаю, когда во время компиляции!)?
Сделать это с изменяемым списком или коллекцией довольно легко, но я добавляю много проблем в свой код (поскольку мы имеем дело с параллелизмом здесь).
Возможно ли, следуя парадигме FP, решить эту проблему, используя безопасные неизменяемые объекты (списки ...)?
Кстати, я новичок в FP, так что этот вопрос может показаться глупым, но я не могу понять, как решить эту проблему :)
Вот как Erlang-подобный актор мог бы выглядеть в Scala:
case class Actor[State](val s: State)(body: State => Option[State]) { // immutable
@tailrec
def loop(s1: State) {
body(s1) match {
case Some(s2) => loop(s2)
case None => ()
}
}
def act = loop(s)
}
def Bot(controller: Actor) = Actor(controller) {
s =>
val res = // do the calculations
controller ! (this, res)
None // finish work
}
val Controller = Actor(Map[Bot, ResultType]()) {s =>
// start bots, perhaps using results already stored in s
if (
// time to stop, e.g. all bots already finished
)
None
else
receive {
case (bot, res) => Some(s + (bot -> res)) // a bot has reported result
}
}
Controller.act
Актеры обычно имеют внутреннее состояние, будучи сами изменчивыми зверями. Обратите внимание, что актеры — это не FP.
Похоже, что описанная вами установка основана на изменяемом контроллере, и ее трудно обойти на языке, который по умолчанию не является нестрогим. Однако в зависимости от того, что вы делаете, вы можете полагаться на фьючерсы. Например:
case Msg(info) =>
val v1 = new Bot !! Fn1(info)
val v2 = new Bot !! Fn2(info)
val v3 = new Bot !! Fn3(info)
val v4 = new Bot !! Fn4(v1(), v2(), v3())
reply(v4())
В данном случае -- потому что !!
возвращает Future
-- v1
, v2
и v3
будут вычисляться параллельно. Сообщение Fn4
принимает фьючерс в качестве параметров, что означает, что он будет ждать, пока не будут вычислены все значения, прежде чем начать вычисления.
Аналогичным образом, ответ будет отправлен только после того, как v4
будет вычислено, поскольку будущее также было подано.
По-настоящему функциональным способом выполнения этих задач является функциональное реактивное программирование, или сокращенно FRP. Это другая модель, чем актеры.
Прелесть Scala, однако, в том, что вы можете комбинировать такие парадигмы в той мере, в какой это лучше подходит для вашей задачи.