Подтвердить что ты не робот

Scala IO monad: какой смысл?

Недавно я просмотрел видео о том, как вы могли прийти к монаде IO, разговор был в scala. Мне действительно интересно, что из-за того, что функции возвращают из них IO [A]. Лямбда-выражения, обернутые в объект IO, являются мутациями и в какой-то момент выше изменения, которое они должны соблюдать, я имею в виду выполненный, так что что-то происходит. Вы не просто подталкиваете проблему выше дерева где-то еще?

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

Мне было интересно, все ли это? Есть ли преимущества в тестируемости? Я предполагаю, что не так, как вам нужно было бы наблюдать эффекты, которые отрицают это. Если вы использовали черты/интерфейсы, вы можете управлять зависимостями, но не тогда, когда эффекты происходят в этих зависимостях.

Я собрал следующий код в коде.

case class IO[+A](val ra: () => A){
  def unsafePerformIO() : A = ra();
  def map[B](f: A => B) : IO[B] = IO[B]( () => f(unsafePerformIO()))
  def flatMap[B](f: A => IO[B]) : IO[B] = {
    IO( () =>  f(ra()).unsafePerformIO())
  }
}



case class Person(age: Int, name: String)

object Runner {

  def getOlderPerson(p1: Person,p2:Person) : Person = 
    if(p1.age > p2.age) 
        p1
      else
        p2

  def printOlder(p1: Person, p2: Person): IO[Unit] = {
    IO( () => println(getOlderPerson(p1,p2)) ).map( x => println("Next") )
  }

  def printPerson(p:Person) = IO(() => {
    println(p)
    p
  })

  def main(args: Array[String]): Unit = {

    val result = printPerson(Person(31,"Blair")).flatMap(a => printPerson(Person(23,"Tom"))
                                   .flatMap(b => printOlder(a,b)))

   result.unsafePerformIO()
  }

}

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

Является ли моя реализация правильной и правильно ли я понимаю.

Мне также интересно, следует ли использовать milage, он должен быть объединен с ValidationMonad, как в ValidationMonad [IO [Person]], чтобы мы могли коротко замыкаться, когда происходят исключения? Мысли, пожалуйста.

Blair

4b9b3361

Ответ 1

Для сигнатуры типа функции важно записать, имеет ли он побочные эффекты. У вашей реализации IO есть ценность, потому что это действительно так. Это делает ваш код лучше документированным; и если вы реорганизуете свой код, чтобы отделить, насколько это возможно, логику, которая включает IO из логики, которая этого не делает, вы сделали функции, не связанные с IO, более сложными и более проверяемыми. Вы можете сделать тот же рефакторинг без явного типа ввода-вывода; но использование явного типа означает, что компилятор может помочь вам выполнить разделение.

Но это только начало. В коде в вашем вопросе действия IO кодируются как лямбда, и поэтому непрозрачны; вы ничего не можете сделать с действием ввода-вывода, кроме запуска его, и его эффект при запуске жестко запрограммирован.

Это не единственный возможный способ реализации монады IO.

Например, я могу использовать классы case IO, которые расширяют общую черту. Затем я могу, например, написать тест, который запускает функцию, и видит, возвращает ли он правильное действие IO.

В тех классах, которые представляют разные виды операций ввода-вывода, я не могу включать жестко закодированные реализации действий, которые выполняются при запуске. Вместо этого я мог бы отделить это от использования шаблона typeclass. Это позволит менять в разных реализациях действия IO. Например, у меня может быть один набор реализаций, которые общаются с производственной базой данных, и другой набор, который говорит о том, что в целях тестирования используется макет базы данных в памяти.

В главе 13 ( "Внешние эффекты и ввод-вывод" ) функциональное программирование Бьярнасона и Чиусано хорошо описано в Scala. См. Особенно 13.2.2, "Преимущества и недостатки простого типа ввода-вывода".

UPDATE (декабрь 2015 г.): re "свопинг в разных реализациях действий IO", в наши дни все больше людей используют "свободную монаду" для такого рода вещей; см., например, John De Goes: "" Современная архитектура для FP".

Ответ 2

Преимущество использования IO-монады состоит в чистых программах. Вы не выдвигаете побочные эффекты выше цепи, но устраняете их. Если у вас есть нечистая функция, например:

def greet {
  println("What is your name?")
  val name = readLine
  println(s"Hello, $name!")
}

Вы можете удалить побочный эффект, переписав его на:

def greet: IO[Unit] = for {
  _ <- putStrLn("What is your name?")
  name <- readLn
  _ <- putStrLn(s"Hello, $name!")
} yield ()

Вторая функция является ссылочно прозрачной.

Очень хорошее объяснение, почему использование модов IO приводит к чистым программам, можно найти в Rúnar Bjarnason слайды от scala.io( видео можно найти здесь).