IO[A]
предназначен для представления Action
, возвращающего A
, где результат действия может зависеть от среды (что угодно, значения переменных, файловая система, системное время ...) ивыполнение действия также может изменить среду.На самом деле тип scala для Action будет Function0
.Function0[A]
возвращает A при вызове, и ему, безусловно, разрешено зависеть и изменять среду.IO
- это Function0
под другим именем, но он предназначен для того, чтобы отличить (пометить?) Те функции0, которые зависят от окружения, от других, которые на самом деле являются чистым значением (если вы говорите, что f - это функция [A], котораявсегда возвращает одно и то же значение, без каких-либо побочных эффектов, нет большой разницы между f
и его результатом).Если быть точным, функция помечается не так сильно, как IO должен иметь побочный эффект.Это то, что те, кто не помечен, не должны иметь ни одного.Обратите внимание, однако, что упаковка нечистых функций в IO
является полностью добровольной, у вас не будет никакой гарантии, когда вы получите Function0
, что она чистая. Использование IO
определенно не является доминирующим стилем в scala .
pure принимает значение и превращает его в значение, содержащееся в типе «контейнер».
Совершенно верно, но «контейнер» может означать довольно много вещей,И тот, кто возвращается чистым, должен быть как можно более легким, он должен быть тем, который не имеет значения.Суть списка в том, что они могут иметь любое количество значений.Тот, кто вернулся чистым, должен иметь его.Дело в том, что ИО зависит от окружающей среды и влияет на нее.Тот, кто вернулся чистым, не должен делать ничего подобного.Так что это на самом деле чистый Function0
() => a
, завернутый в IO
.
bind почти так же, как map
Не так, bind - это то же самое, что flatMap.Когда вы пишете, map получит функцию от Int
до String
, но здесь у вас есть функция от Int
до List[String]
Теперь, забудьте на мгновение IO и подумайте, что связывает /flatMap будет означать для действия, то есть для Function0
.Давайте получим
val askUserForLineNumber: () => Int = {...}
val readingLineAt: Int => Function0[String] = {i: Int => () => ...}
Теперь, если мы должны объединить, как это делает bind / flatMap, эти элементы, чтобы получить действие, которое возвращает строку, то, что должно быть, довольно ясно: спросите у читателя номер строки,прочитайте эту строку и верните ее.Это было бы
val askForLineNumberAndReadIt= () => {
val lineNumber : Int = askUserForLineNumber()
val readingRequiredLine: Function0[String] = readingLineAt(line)
val lineContent= readingRequiredLine()
lineContent
}
Более обобщенно
def bind[A,B](a: Function0[A], f: A => Function0[B]) = () => {
val value = a()
val nextAction = f(value)
val result = nextAction()
result
}
И короче:
def bind[A,B](a: Function0[A], f: A => Function0[B])
= () => {f(a())()}
Итак, мы знаем, что bind
должно быть для Function0
, pure
тоже понятно.Мы можем сделать
object ActionMonad extends Monad[Function0] {
def pure[A](a: => A) = () => a
def bind[A,B](a: () => A, f: A => Function0[B]) = () => f(a())()
}
Теперь IO - это скрытая функция 0.Вместо того, чтобы просто делать a(),
, мы должны сделать a.unsafePerformIO
.И чтобы определить один вместо () => body
, мы пишем IO {body}
Так что может быть
object IOMonad extends Monad[IO] {
def pure[A](a: => A) = IO {a}
def bind[A,B](a: IO[A], f: A => IO[B]) = IO {f(a.unsafePerformIO).unsafePerformIO}
}
На мой взгляд, этого было бы достаточно.Но на самом деле это повторяет ActionMonad.Смысл в коде, на который вы ссылаетесь, состоит в том, чтобы этого избежать и повторно использовать то, что сделано для Function0
.Можно легко перейти с IO
до Function0
(с () => io.unsafePerformIo
), а также с Function0
до IO
(с IO { action() }
).Если у вас есть f: A => IO [B], вы также можете изменить это значение на f: A => Function0[B]
, просто составив преобразование от IO
до Function0
, поэтому (x: A) => f(x).unsafePerformIO
.
То, что происходит здесь в привязке к IO:
() => a.unsafePerformIO
: превращение a
в Function0
(x:A) => () => f(x).unsafePerformIO)
: поворот f
в A => Function0[B]
- неявно [Monad [Function0]]: получить монаду по умолчанию для
Function0
, то же самое, что ActionMonad
выше bind(...)
: применить bind
монады Function0
к аргументам a
и f
, которые только что были преобразованы в Function0 - Включая
IO{...}
: преобразовать результат обратно в IO
,
(Не уверен, что мне это очень нравится)