В Scala Функциональном программировании есть ли идиоматический c способ сопоставления с состоянием? - PullRequest
2 голосов
/ 06 апреля 2020

Обычная функция отображения имеет подпись A => B, чтобы преобразовать F[A] в F[B], например List[A] в List[B].

Но что, если вы это сделаете если функция отображения должна нести некоторое состояние, вдоль которого требуется вычисление B?

Скажем, функция отображения выглядит следующим образом: (A, S) => (B, S), где S - тип State. Для каждого A ранее возвращенный S передается в функцию отображения, в то время как первоначально для состояния предоставляется элемент zero. Затем функция отображения возвращает новое состояние (вместе с результатом), которое затем снова передается вместе со следующим значением и т. Д.

Конечно, .map недостаточно мощно, чтобы сделать это, поэтому решение должно основываться на другом операторе.

Для наглядности приведу конкретный пример, скажем, у меня есть последовательность Ints, и я хочу вычислить разницу каждого Int с предыдущим Int в этой последовательности. Реализация функции отображения, как описано выше, будет выглядеть следующим образом:

  def mapping(currentElement: Int, previousElement: Option[Int]): (Option[Int], Option[Int]) = {
    (previousElement.map(currentElement - _), Some(currentElement))
  }

Начальное значение zero для previousElement будет None, а после первого элемента оно всегда будет Some(currentElement). Результатом для каждой итерации будет Some текущего значения за вычетом последнего значения, за исключением первого элемента, где он равен None.

Как преобразовать, например, List(1, 4, 3) в List(None, Some(3), Some(-1)) Использование функции mapping?

(Обратите внимание, что пример Int-вычитания приведен исключительно для иллюстративных целей, а фокус вопроса - универсальное решение для описанного типа операции.)

Ответы [ 3 ]

3 голосов
/ 06 апреля 2020

Метод Scala 2.13.x unfold() поддерживает состояние, которое переносится аналогично вашему примеру.

List.unfold((Option.empty[Int], List(1, 4, 3))){
  case (prev, hd::tl) => Some((prev.map(hd.-), (Some(hd),tl)))
  case (prev, Nil)    => None
}
//res0: List[Option[Int]] = List(None, Some(3), Some(-1))

Это доступно в LazyList и Iterator, поэтому его можно использовать создать псевдо-бесконечный поток.

2 голосов
/ 06 апреля 2020

Есть несколько библиотек для выполнения передачи состояния "mtl-style", что вы и описываете. (Проверьте подписи типов после этого фрагмента)

import cats._
import cats.data._
import cats.implicits._

// Given an element and state, calculate next state and return value
def modifyEntry(currentElement: Int): State[Option[Int], Option[Int]] = for {
  previousElement <- State.get
  _ <- State.set[Option[Int]](Some(currentElement)) // Next State
} yield previousElement map (currentElement - _) // Calculated Value

// It is useful for hiding state and passing it implicitly
val result =
  for {
    val1 <- modifyEntry(1)
    val2 <- modifyEntry(2)
    val3 <- modifyEntry(3)
  // Final state is implicittly stored in the yielded State[Option[Int], Seq[Option[Int]]]
  } yield Seq(val1, val2, val3) 

// Run with None initial State and coerce evaluation (cats is lazy by default)
println("for-comprehension result (final state and value): " -> result.run(None).value)

// More importantly, it is _easy_ to compose with Traversables or other generic cats traits
println("traverse result (only value): " ->
  List(1,2,3).traverse(modifyEntry).runA(None).value) // List(None, Some(1), Some(1))
println("traverse result (only value): " ->
  List(1,4,3).traverse(modifyEntry).runA(None).value) // List(None, Some(3), Some(-1))

Вас особенно заинтересует черта StateFunctions от scalaz или State от cats. Сравнение: https://github.com/fosskers/scalaz-and-cats

Scalaz StateFunctions:

trait StateFunctions extends IndexedStateFunctions {
  // ...
  def get[S]: State[S, S] = State(s => (s, s))

  def put[S](s: S): State[S, Unit] = State(_ => (s, ()))
  // ...
}

Cats StateFunctions с некоторыми модификациями:

abstract private[data] class StateFunctions {
  // ...
  def get[S]: State[S, S] = ??? // Some other code like State(s => (s, s))

  def set[S](s: S): State[S, Unit] = State(_ => (s, ()))
}

Для кошек, проверьте отличные документы с некоторыми другими примерами: https://typelevel.org/cats/datatypes/state.html

Для скалаза, здесь есть хороший доклад с обзором "mtl-style" в Scala и Скалаза: Павел Сул c - ПОЛУЧИТЬ БОЛЬШЕ УСТРОЙСТВА ИЗ ВАШИХ МОНАД С MTL , хотя остерегайтесь

Для любого из них остерегайтесь минусов MonadTransformers (не в стиле / чертах mtl, см. Вторую часть) : http://degoes.net/articles/effects-without-transformers

1 голос
/ 06 апреля 2020

"Оператор", который вы ищете, это fold:

List(1, 4, 3).foldLeft(None: Option[Int], List[Option[Int]]())
   ((acc, curr) => (Some(curr), acc._1.map(_ - curr) :: acc._2))
    ._2
    .reverse

Еще один способ думать об этом - использовать zip:

val xs = List(1, 4, 3)

val result = None :: xs.zip(xs.drop(1)).map(currAndNext => Some(currAndNext._2 - currAndNext._1))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...