Как в Scala я бы совмещал программирование на основе событий с функциональным подходом?

Чтобы уточнить, что я имею в виду под управлением событиями, я имею в виду ситуацию, когда у меня есть

def onTrade(...)

Который вызывается каждый раз, когда торгуется определенная акция. Предположим, я хочу отслеживать дневную максимальную цену сделки. Для меня очевидным решением является:

var dailyHigh = 0

def onTrade(...) {
    if (price > dailyHigh) dailyHigh = price
}

Есть ли способ достичь этой функциональности, используя val вместо var? Предположим также, что я могу захотеть добавить dailyLow, volumeHigh, volumeLow и т. Д. В будущем.

5 ответов

Решение

Не большая проблема, на самом деле. Полное решение, вероятно, будет использовать Reader, IO и State монады плюс Iteratee и линзы, но вот более простая версия:

case class State(dailyHigh: Int = 0)

object Main {
  type Event = (State => State)

  def mainLoop(currState: State, events: Stream[Event]): State =
    if (events.nonEmpty) {
      val newState = events.head(currState)
      mainLoop(newState, events.tail)
    } else currState

  def onTrade(price: Int): Event = (s: State) =>
    if (price > s.dailyHigh) s.copy(dailyHigh = price) else s

  def main(args: Array[String]) {
    val events = onTrade(5) #:: onTrade(2) #:: onTrade(10) #:: onTrade(5) #:: Stream.empty
    val finalState = mainLoop(State(), events)
    println(finalState)
  }
}

Смотри, ма, никаких перемен!

Конечно, состояние может быть довольно сложным, но вот здесь и появляются линзы. С линзами довольно легко консультироваться и изменять (копировать с новым значением) произвольно сложные структуры данных.

Использование итераторов естественно для событий - в очень простом смысле "onTrade" становится итератором, который вызывается перечислителем (вещь, которая "генерирует" события) с каждым событием, если оно составлено из частичной функции, вы можете сложить все их в одну частичную функцию.

В качестве альтернативы, монады состояний могут быть объединены с монадами ввода-вывода в целях понимания.

Наконец, есть возможность продолжения. Если для некоторой обработки требуется цепочка событий, то результат каждого события может быть продолжением, а сами продолжения становятся частью состояния.

Документ " Устаревшая картина наблюдателя" может быть интересен, но я считаю, что библиотека, которую он описывает, еще не доступна.

Иногда изменяемый статус необходим естественным образом. Ниже приведен пример из книги "scala by example".
Он также имеет некоторый изменчивый статус (maxBid,maxBidder). Так что переменная не всегда плохая идея. Иногда работает нормально.

   class Auction(seller: Actor, minBid: Int, closing: Date) extends Actor {
   val timeToShutdown = 36000000 // msec
   val bidIncrement = 10
   def act() {
      var maxBid = minBid - bidIncrement
      var maxBidder: Actor = null
      var running = true
      while (running) {
         receiveWithin((closing.getTime() - new Date().getTime())) {
            case Offer(bid, client) =>
               if (bid >= maxBid + bidIncrement) {
                  if (maxBid >= minBid) maxBidder ! BeatenOffer(bid)
                  maxBid = bid; maxBidder = client; client ! BestOffer
               } else {
                  client ! BeatenOffer(maxBid)
               }
            case Inquire(client) =>
               client ! Status(maxBid, closing)
            case TIMEOUT =>
               if (maxBid >= minBid) {
                  val reply = AuctionConcluded(seller, maxBidder)
                  maxBidder ! reply; seller ! reply
               } else {
                  seller ! AuctionFailed
               }
               receiveWithin(timeToShutdown) {
                  case Offer(_, client) => client ! AuctionOver
                  case TIMEOUT          => running = false
               }
         }
      }
   }
}

Я настоятельно рекомендую функциональное реактивное программирование для этой задачи. Вот разговор о такой библиотеке в Scala: http://skillsmatter.com/podcast/scala/reactors

На самом деле никогда не делал этого, но вместо изменения значений вы можете создавать новые экземпляры в потоке.

Затем другой процесс мог бы выполнить итерацию этого потока, что заставило бы их ждать, когда они достигнут последнего экземпляра элемента потока.

Другие вопросы по тегам