Как остановить переход состояния, когда состояние удовлетворяет некоторому условию?

Я довольно новичок в скалязе / кошках и у меня есть вопрос о State монада (cats или же scalaz не имеет значения). Рассмотрим следующий хорошо известный пример с Stack:

object StateTest {

  type Stack = List[Int]

  def main(args: Array[String]) = {
    println(transition.run(List(1, 2, 3, 4)).value) //(List(4),Some(3))
    println(transition.run(List(1, 2)).value) //(List(),None)

  }

  def transition: State[Stack, Option[Int]] = for {
    _ <- pop
    _ <- pop
    a <- pop
  } yield a

  def pop: State[Stack, Option[Int]] = State {
    case x::xs => (xs, Some(x))
    case Nil => (Nil, None)
  }
}

Проблема в том, что я хочу выполнить переход состояния (pop) до состояния (List[Int]) удовлетворяет некоторому условию (хочу проверить List[Int]::isEmpty) и после этого сразу же остановиться.

В текущей реализации я могу только знать, удовлетворяет ли состояние условию после того, как я вызываю run,

Можно ли это сделать в кошках / скалазах с помощью государственной монады или мне нужно что-то еще?

1 ответ

Решение

Вы бы использовали монаду состояния, параметризованную другой монадой, представляющей завершение.

Обычно такие параметризованные монады называют монадными трансформаторами. В этом конкретном случае вы бы использовали StateT монадный трансформатор. По модулю некоторые детали реализации, StateT эквивалентно

type StateT[F[_], S, A] = S => F[(S, A)]

Теперь вы можете выбрать F быть Option, представляющий немедленное прекращение.

import scalaz.StateT
import scalaz.std.option._

object StateTest {

  type Stack = List[Int]

  def main(args: Array[String]) = {
    println(transition.run(List(1, 2, 3, 4))) // Some((List(4), 3))
    println(transition.run(List(1, 2)))       // None
  }

  def transition: StateT[Option, Stack, Int] = for {
    _ <- pop
    _ <- pop
    a <- pop
  } yield a

  def pop: StateT[Option, Stack, Int] = StateT {
    case x::xs => Some((xs, x))
    case Nil   => None
  }
}

Если вы хотите вернуть некоторое значение типа B даже в случае досрочного расторжения вы можете использовать Either[B, ?] вместо Option параметризовать StateT:

type ErrorOr[A] = Either[String, A]

def pop1: StateT[ErrorOr, Stack, Int] = StateT {
  case x::xs => Right((xs, x))
  case Nil   => Left("Cannot pop from an empty stack.")
}
Другие вопросы по тегам