Как в образце Simple IO Type избавиться от побочных эффектов в "FP in Scala"?
Я читаю главу 13.2.1 и наткнулся на пример, который может обрабатывать ввод ввода-вывода и тем временем избавляться от побочных эффектов:
object IO extends Monad[IO] {
def unit[A](a: => A): IO[A] = new IO[A] { def run = a }
def flatMap[A,B](fa: IO[A])(f: A => IO[B]) = fa flatMap f
def apply[A](a: => A): IO[A] = unit(a)
}
def ReadLine: IO[String] = IO { readLine }
def PrintLine(msg: String): IO[Unit] = IO { println(msg) }
def converter: IO[Unit] = for {
_ <- PrintLine("Enter a temperature in degrees Fahrenheit: ")
d <- ReadLine.map(_.toDouble)
_ <- PrintLine(fahrenheitToCelsius(d).toString)
} yield ()
У меня есть пара вопросов относительно этого куска кода:
- в
unit
функция, что делаетdef run = a
действительно? - в
ReadLine
функция, что делаетIO { readLine }
действительно? Будет ли это действительно выполнитьprintln
функция или просто вернуть тип ввода-вывода? - Что значит
_
в смысле понимания (_ <- PrintLine("Enter a temperature in degrees Fahrenheit: ")
) - Почему это устраняет побочные эффекты IO? Я видел, что эти функции все еще взаимодействуют с входами и выходами.
2 ответа
Определение вашего
IO
как следует:trait IO { def run: Unit }
Следуя этому определению, вы можете понять, что написание
new IO[A] { def run = a }
означает инициализацию анонимного класса из вашей черты и назначениеa
быть методом, который запускается при вызовеIO.run
, Так какa
является параметром по имени, на самом деле ничего не запускается во время создания.Любой объект или класс в Scala, следующий за контрактом
apply
метод, может быть вызван как:ClassName(args)
где компилятор будет искатьapply
метод объекта / класса и преобразовать его вClassName.apply(args)
вызов. Более подробный ответ можно найти здесь. Как таковой, потому чтоIO
сопутствующий объект обладает таким методом:def apply[A](a: => A): IO[A] = unit(a)
Расширение допускается. Таким образом, мы на самом деле называем
IO.apply(readLine)
вместо._
имеет много перегруженных применений в Scala. Это означает, что "меня не волнует значение, возвращаемое изPrintLine
"Откажитесь от него". Это так, потому что возвращаемое значение имеет тип Unit, к которому мы не имеем никакого отношения.Это не то, что
IO
Тип данных удаляет часть выполнения ввода-вывода, это то, что он откладывает его на более поздний момент времени. Обычно мы говорим, что IO работает на "краях" приложения, вMain
метод. Эти взаимодействия с внешним миром все еще будут происходить, но, поскольку мы инкапсулируем их в IO, мы можем рассуждать о них как о ценностях в нашей программе, что приносит большую пользу. Например, теперь мы можем составлять побочные эффекты и зависеть от успеха / неудачи их выполнения. Мы можем смоделировать эти эффекты ввода-вывода ( используя другие типы данных, такие какConst
) и многие другие удивительно приятные свойства.
Самый простой способ посмотреть на IO
монада как маленький кусочек определения программы.
Таким образом:
- Это
IO
определение,run
метод определяет, чтоIO
Монада делает.new IO[A] { def run = a }
это Scala способ создания экземпляра класса и определения методаrun
, - Там немного синтаксического сахара происходит.
IO { readLine }
такой же какIO.apply { readLine }
или жеIO.apply(readLine)
гдеreadLine
является функцией вызова по имени типа=> String
, Это вызываетunit
метод изobject IO
и, таким образом, это просто создание экземпляраIO
класс, который еще не работает. - поскольку
IO
это монада, для понимания можно использовать. Требуется сохранение результата каждой монады в синтаксисе, подобномresult <- someMonad
, Чтобы игнорировать результат,_
можно использовать, таким образом,_ <- someMonad
читает как выполнить монаду, но игнорирует результат. - Это все методы
IO
определения, они ничего не запускают и, следовательно, нет побочного эффекта. Побочные эффекты появляются только тогда, когдаIO.run
называется.