Понимание Scala для понимания в этом примере кода - PullRequest
0 голосов
/ 13 января 2020

Я новичок в этом Scala мире и у меня есть вопросы по поводу следующего кода.

sealed trait Input
case object Coin extends Input
case object Turn extends Input

case class Machine(locked: Boolean, candies: Int, coins: Int)

object Candy {
  def update = (i: Input) => (s: Machine) =>
    (i, s) match {
      case (_, Machine(_, 0, _)) => s
      case (Coin, Machine(false, _, _)) => s
      case (Turn, Machine(true, _, _)) => s
      case (Coin, Machine(true, candy, coin)) =>
        Machine(false, candy, coin + 1)
      case (Turn, Machine(false, candy, coin)) =>
        Machine(true, candy - 1, coin)
    }

  def simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = for {
    _ <- sequence(inputs map (modify[Machine] _ compose update))
    s <- get
  } yield (s.coins, s.candies)
}

case class State[S, +A](run: S => (A, S)) {
  def map[B](f: A => B): State[S, B] =
    flatMap(a => unit(f(a)))
  def map2[B,C](sb: State[S, B])(f: (A, B) => C): State[S, C] =
    flatMap(a => sb.map(b => f(a, b)))
  def flatMap[B](f: A => State[S, B]): State[S, B] = State(s => {
    val (a, s1) = run(s)
    f(a).run(s1)
  })
}
object State {
    def modify[S](f: S => S): State[S, Unit] = for {
        s <- get // Gets the current state and assigns it to `s`.
        _ <- set(f(s)) // Sets the new state to `f` applied to `s`.
    } yield ()

    def get[S]: State[S, S] = State(s => (s, s))

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

    def sequence[S,A](sas: List[State[S, A]]): State[S, List[A]] =
        sas.foldRight(unit[S, List[A]](List()))((f, acc) => f.map2(acc)(_ :: _))

    def unit[S, A](a: A): State[S, A] =
        State(s => (a, s))
}
  1. В методе simulateMachine, что такое первый и второй _? По ощущениям, первый - игнорируемый вывод, эти две подставки означают одно и то же значение?
  2. get из State, как он получает состояние Machine?
  3. yield output кортеж, как он соответствует типу возврата State[Machine, (Int, Int)]
  4. Как вызвать этот метод simulateMachine? Похоже, мне нужно где-то инициировать состояние машины.

По ощущениям этот документ не может помочь мне понять для понимания под капотом, вы также можете поделиться ссылкой, чтобы помочь мне понять это лучше? Спасибо!

1 Ответ

2 голосов
/ 13 января 2020

Вот немного более подробная версия simulateMachine, которая должна быть немного проще для понимания.

def simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = for {
    _ <- State.sequence(  // this _ means "discard this 'unpacked' value"
      inputs.map(
        (State.modify[Machine] _).compose(update) // this _ is an eta-expansion
      )
    )
    s <- State.get
  } yield (s.coins, s.candies)

Значение "Unpacked" - for { value <- container } ... в основном "unpacks" value от container. Семантика распаковки в разных контейнерах различна - наиболее простой является семантика List или Seq, где value каждый элемент семантики value и for в этом случае становится просто итерацией. Другие известные «контейнеры»:

  • Option - for семантика: значение присутствует или нет + модификации значения, если оно присутствует
  • Try - for семантика : успешное вычисление или нет + модификации успешной
  • Future - for семантика: определение цепочки вычислений для выполнения при наличии значения

Практически каждая монада ( объяснение, что такое монада, является haaard :) Попробуйте это в качестве отправной точки) определяет способ "распаковать" и "итерировать". Ваш State практически является монадой (хотя он и не заявляет об этом явно) (и, кстати, есть каноническая State монада, которую вы можете найти в сторонних библиотеках, таких как cats или scalaz )

Эта-расширение объясняется в этом SO ответ , но вкратце он преобразует функцию / метод в объект функции. Примерно эквивалентно x => State.modify[Machine](x)

или полностью отлаженной версии:

def simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = {
    val simulationStep: Input => State[Machine, Unit] = (State.modify[Machine] _).compose(update) // this _ is an eta-expansion

    State.sequence(inputs.map(input => simulationStep(input)))
      .flatMap(_ => State.get)  // `_ => xyz` creates a function that does not care about it's input
      .map(machine => (machine.candies, machine.coins))
  }

Обратите внимание, что использование _ в _ => State.get - это еще один способ использования _ - сейчас он создает функцию, которая не заботится о своем аргументе .

Теперь, отвечая на вопросы, когда они задаются:

  1. См. Выше:)
  2. Если вы посмотрите на десагаратную версию, вы увидите, что State.get передается как "обратный вызов" flatMap. Есть несколько обручей, чтобы прыгнуть, чтобы проследить это (я оставлю это как упражнение для вас :)), но, по сути, это то, что он заменяет часть «результата» состояния (A) состоянием сам (S)). Хитрость заключается в том, что он вообще не получает Machine - он просто «цепляет» вызов для «извлечения» Machine из S части состояния в A часть состояния.
  3. Я думаю, что версия от desugared должна быть достаточно понятной. Что касается версии для понимания - for исправляет «контекст», используя первый вызов в цепочке, поэтому все остальное (включая yield) выполняется в этом «контексте» - в частности, оно устанавливается на State[_, _]. yield по существу эквивалентно вызову map в конце цепочки, а в семантике State[_, _] map заменяет часть A, но сохраняет часть S.
  4. Candy.simulateMachine(List(Coin, Turn, Coin, Turn)).run(Machine(false, 2, 0))

PS Ваш текущий код по-прежнему пропускает State.set метод. Я предполагаю, что это просто def set[S](s: => S): State[S, S] = State(_ => (s, s)) - по крайней мере, добавив это, вы сможете компилировать ваш фрагмент в моей среде.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...