Почему не выполняется IO, вложенный в другие монады? Есть ли способ заставить их?

Это продолжение моего последнего вопроса. Действие IO, вложенное в другие монады, не выполняющиеся

Решением этого вопроса было удаление некоторых монад, что позволило выполнить действие ввода-вывода.

Почему мне нужно было развернуть монады? Есть ли способ выполнить IO, не отменяя?

Примечание: это вопрос " что, если", а не вопрос хорошей или плохой практики.

3 ответа

Решение

Возможно, это поможет думать о IO как type IO a = World -> (a, World); то есть функция, которая принимает в качестве единственного параметра текущее состояние вашего компьютера и возвращает новое состояние вместе с некоторым значением a, Это не слишком отличается от фактической реализации IO во внутренних органах GHC, мы надеемся, что мы можем простить (неприемлемый) способ общения по аналогии здесь.

Так readFile :: FilePath -> IO String, например, становится readFile :: FilePath -> World -> (a, World),

А также main :: IO () действительно main :: World -> ((), World),

Однако это означает, что значения с типом IO _ инертны. Они просто функции! Функции не могут ничего делать, пока им не присвоено значение; в нашем случае значение, которое хочет функция, является World объект, который у нас нет возможности построить. В этом вся прелесть IO в Haskell: мы можем создать IO действие с использованием монадических операторов, которые мы знаем и любим (возврат, связывание), но он ничего не может сделать, пока время выполнения не пройдет в World объект.

Что означает, что любой IO действие, которое мы строим, не пронизывающее main не будет выполнен

Итак, с foobar :: [Char] -> IO [IO ()]Мы, конечно, можем наблюдать возвращаемое значение:

main :: IO ()
main = do
  ios <- foobar "string"
  print "goodbye"

Но это не так, пока мы не разобрать ios и связать внутреннее IO значения, которые эти действия получают World они желают:

main :: IO ()
main = do
  ios <- foobar
  ios !! 0
  ios !! 1
  ios !! 2
  ...
  print "goodbye"

Или, для краткости,

main = do
  ios <- foobar
  sequence ios
  print "goodbye"

Надеюсь это поможет.

Давайте начнем с немного другого примера. Как вы знаете, String это список Char:

GHCi> :set +t
GHCi> "Mississippi"
"Mississippi"
it :: [Char]

Список Strings это список списков Char; это [[Char]]:

GHCi> group "Mississippi"
["M","i","ss","i","ss","i","pp","i"]
it :: [[Char]]

group "Mississippi" это [[Char]]и я не хочу, чтобы это обрабатывалось как [Char] - это победило бы смысл использования group,

IO a Значение для большинства целей является значением, как и любое другое, и поэтому применяются аналогичные соображения. Чтобы привести конкретный ( и реалистичный) пример, предположим, что у нас есть функция с этим типом...

(KeyCode -> IO ()) -> IO (IO ())

... который регистрирует обработчики событий для нажатия клавиш в графическом интерфейсе. Идея состоит в том, что вы вызываете функцию с KeyCode -> IO () аргумент, который указывает, что должно произойти в ответ на нажатие клавиши, и запустить IO (IO ()) так что вы выбрали KeyCode -> IO () Обработчик становится активным. Внутренний IO () производится IO (IO ()) действие, однако, служит другой цели: оно отменяет регистрацию обработчика событий и предназначено для использования на более позднем этапе приложения по вашему усмотрению - возможно, никогда. В этом случае вы определенно не хотите запускать внутреннее действие сразу после внешнего!

Подводя итог, IO (IO a) является IO действие, которое при запуске производит другое IO действие, которое вы можете или не можете выполнять также.

PS: как Шейл упоминал в других вопросах и ответах, join может быть использован для выравнивания вложенного IO действие или любое другое вложенное монадическое значение. Кстати, списки также имеют Monad пример. Как вы думаете join (group "Mississippi") Сделаю?

Ну... ты задаешь вопрос

Почему мне нужно было развернуть монады? Есть ли способ выполнить IO, не отменяя?

Что ж, позвольте мне сказать это как можно более резко: Unnesting или join То, как мы это называем в Хаскел- ланде, - это не просто еще один комбинатор монад, это особая святая вещь, которая отличает Monad с из Functor а также Applicative!

Да, это означает, что Monad Класс типа мог быть разработан с join метод вместо >>=,

На самом деле unnesting и обязательные две разные перспективы для одного и того же!

Позвольте мне испортить остаток этого поста:

join = (>>= id)                  

... а также:

(ma >>= amb) = join (amb <$> ma)

Давайте докажем их равными, показав, что мы можем сделать join из >>= и наоборот.

Изготовление join от >>=

Хорошо теперь join = (>>= id) более подробно:

join mmx = do mx <- mmx
              x <- mx
              return x

а потом:

join mmx = do mx <- mmx
              mx

а теперь с bind ака >>=:

join mmx = mmx >>= id

и указать бесплатно, используя разделы:

join = (>>= id)

Изготовление >>= от join

Теперь наоборот, это сложнее, нам нужен тот факт, что каждый Monad это Functor тоже.

Помни что >>= Actaully Do ES (каламбур предназначен):

ma >>= amb = do a <- ma
                amb a

Мы знаем это amb является функцией типа a -> m b, идея состоит в том, чтобы использовать fmap у которого есть (c -> d) -> m c -> m d, если мы fmapamb мы получаем выражение fmap amb затем c становится a а также d становится m b а также m c -> m d следовательно становится m a -> m (m b)!

Теперь мы в восторге: m (m b) просто кричит join нам нужно только положить в m a что мы можем сделать с обычным приложением:

ma >>= amb = do mb <- fmap amb ma
                mb

и это то, что мы видели в предыдущем разделе:

join mmb = do mb <- mmb
              mb

с mmb == fmap amb ma

ma >>= amb == do mb <- fmap amb ma == join (fmap amb ma)
                 mb

Вот и ты.

Другие вопросы по тегам