Функциональный слушатель с состоянием - PullRequest
0 голосов
/ 27 июня 2018

Допустим, в моей чистой программе Scala у меня есть зависимость от службы Java. Этот Java-сервис принимает прослушиватель для уведомления меня при изменении некоторых данных.

Допустим, данные являются кортежем (x, y), и java-сервис вызывает слушатель всякий раз, когда изменяется X или Y, но меня интересует только когда X.

Для этого мой слушатель должен сохранить последнее значение X и переслать обновление / вызов только тогда, когда oldX! = X, поэтому, чтобы иметь это, моя нечистая реализация слушателя scala должна содержать переменную oldX

val listener = new JavaServiceListener() {

 var oldX;
 def updated(val x, val y): Unit = {
  if (oldX != x) {
     oldX = x
    //do stuff
  }
}

javaService.register(listener)

Как я собираюсь создать оболочку для такого рода вещей в Scala без val или изменяемых коллекций? Я не могу на уровне JavaServiceListener, так как я связан сигнатурой метода, поэтому мне нужен еще один уровень, выше которого слушатель java пересылает как-то

Ответы [ 3 ]

0 голосов
/ 27 июня 2018

Я бы предпочел обернуть его в Monix Observable , тогда вы можете использовать distinctUntilChanged для устранения последовательных дубликатов. Что-то вроде:

import monix.reactive._

val observable = Observable.create(OverflowStrategy.Fail(10)){(sync) =>
    val listener = new JavaServiceListener() {
      def updated(val x, val y): Unit = {
        sync.onNext(x)
      }
    }

    javaService.register(listener)
    Cancelable{() => javaService.unregister(listener)}
  }

val distinctObservable = observable.distinctUntilChanged

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

0 голосов
/ 30 октября 2018

Я нашел решение, которое мне нравится с Cats и Cats-Effect:

trait MyListener {
  def onChange(n: Int): Unit
}

class MyDistinctFunctionalListener(private val last: Ref[IO, Int], consumer: Int => Unit) extends MyListener {
  override def onChange(newValue: Int): Unit = {
    val program =
      last
        .getAndSet(newValue)
        .flatMap(oldValue => notify(newValue, oldValue))

    program.unsafeRunSync()
  }

  private def notify(newValue: Int, oldValue: Int): IO[Unit] = {
    if (oldValue != newValue) IO(consumer(newValue)) else IO.delay(println("found duplicate"))
  }
}

object MyDistinctFunctionalListener {
  def create(consumer: Int => Unit): IO[MyDistinctFunctionalListener] =
    Ref[IO].of(0).map(v => new MyDistinctFunctionalListener(v, consumer))
}

val printer: Int => Unit = println(_)

val functionalDistinctPrinterIO =  MyDistinctFunctionalListener.create(printer)

functionalDistinctPrinterIO.map(fl =>
  List(1, 1, 2, 2, 3, 3, 3, 4, 5, 5).foreach(fl.onChange)
).unsafeRunSync()

Подробнее об обработке общего состояния здесь https://github.com/systemfw/scala-italy-2018

это спорно, если это стоит над частным решением вара

0 голосов
/ 27 июня 2018

Прежде всего, если вы разрабатываете чисто функциональную программу, вы не можете вернуть Unit (ни Future[Unit], потому что Future не подавляет побочные эффекты).

Если производительность не является проблемой, я бы использовал Kleisli[Option, xType, IO[Unit]], где T = Option. Итак, первое, что вам нужно сделать, это определить (добавить соответствующие типы)

def updated(oldX, x): Kleisli[Option, xType, xType] = Kleisli liftF {
   if(x != oldX) None
   else Some(x)
}

def doStuff(x, y): Kleisli[Option, xType, IO[Unit]] = Kleisli pure {
    IO{
       //doStuff
    }
}

и теперь вы можете составить их для понимания чего-то подобного:

val result: Kleisli[Option, xType, IO[Unit]] = for{
   xx <- updated(oldX, x)
   effect <- doStuff(xx, y)
} yield effect

Вы можете выполнять вычисление с учетом состояния с помощью ReaderWriterStateT, поэтому вы сохраняете oldX как состояние.

...