Увидев, как монады списка и Maybe определены, мне, естественно, стало любопытно, как
операции >>=
и return
определены для монады IO.
Каковы определения для > >= и возврата для монады IO?
Ответ 1
Для IO
нет конкретной реализации; это абстрактный тип, с точной реализацией слева от undefined по Haskell Report. В самом деле, ничто не останавливает реализацию, реализующую IO
и ее экземпляр Monad
как примитивы компилятора, без реализации Haskell вообще.
В принципе, Monad
используется как интерфейс к IO
, который сам не может быть реализован в чистом Haskell. Вероятно, все, что вам нужно знать на этом этапе, и погружение в детали реализации, скорее всего, просто смутит, а не даст понимание.
Тем не менее, если вы посмотрите на исходный код GHC, вы обнаружите, что он представляет IO a
как функцию, похожую на State# RealWorld -> (# State# RealWorld, a #)
(используя unboxed tuple как возвращаемый тип), но это вводит в заблуждение; это деталь реализации, и эти значения State# RealWorld
фактически не существуют во время выполнения. IO
не является государственной монадой, 1 в теории или на практике.
Вместо этого GHC использует нечистые примитивы для реализации этих операций ввода-вывода; значения State# RealWorld
"только для того, чтобы остановить операторы переупорядочения компилятора, введя зависимости данных от одного оператора к следующему.
Но если вы действительно хотите увидеть реализацию GHC return
и (>>=)
, вот они:
returnIO :: a -> IO a
returnIO x = IO $ \ s -> (# s, x #)
bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s
где unIO
просто разворачивает функцию из конструктора IO
.
Важно отметить, что IO a
представляет описание нечистого вычисления, которое может быть выполнено для создания значения типа a
. Тот факт, что существует способ получить значения из внутреннего представления GHC IO
, не означает, что это выполняется в целом или что вы можете сделать такую вещь для всех монадов. Это исключительно детализация со стороны GHC.
1 Государственная монада - монада, используемая для доступа и изменения состояния через серию вычислений; он представлен как s -> (a, s)
(где s
- тип состояния), который очень похож на тип GHC использует для IO
, таким образом, путаницу.
Ответ 2
Вы будете разочарованы, но монада >>=
in IO
не интересна. Чтобы указать источник GHC:
{- |
A value of type @'IO' [email protected] is a computation which, when performed,
does some I\/O before returning a value of type @[email protected]
There is really only one way to \"perform\" an I\/O action: bind it to
@[email protected] in your program. When your program is run, the I\/O will
be performed. It isn't possible to perform I\/O from an arbitrary
function, unless that function is itself in the 'IO' monad and called
at some point, directly or indirectly, from @[email protected]
'IO' is a monad, so 'IO' actions can be combined using either the do-notation
or the '>>' and '>>=' operations from the 'Monad' class.
-}
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
Это означает, что IO
monad объявляется как экземпляр State
State#
monad, и там определен его >>=
(и его реализация довольно легко угадать )забастовкa > .
Подробнее о монаде IO
см. IO внутри в статье о Haskell wiki.
Также полезно посмотреть Haskell docs, где каждая позиция имеет небольшую ссылку "Источник" справа.
Обновление: И еще одно разочарование, это мой ответ, потому что я не заметил "#" в State#
.
Однако IO
ведет себя как State
монада, несущая абстрактный RealWorld
состояние
Как @ehird написал State#
является компилятором internal и >>=
для IO
монада определена в GHC.Base module
instance Monad IO where
{-# INLINE return #-}
{-# INLINE (>>) #-}
{-# INLINE (>>=) #-}
m >> k = m >>= \ _ -> k
return = returnIO
(>>=) = bindIO
fail s = failIO s
returnIO :: a -> IO a
returnIO x = IO $ \ s -> (# s, x #)
bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s
Ответ 3
Они не делают ничего особенного, и просто для последовательности действий. Это поможет, если вы подумаете о них с разными именами:
→ = становится ", а затем, используя результат предыдущего действия,"
→ становится ", а затем"
return становится "ничего не делать, но результат ничего не делает"
Эта функция:
main :: IO ()
main = putStr "hello"
>> return " world"
>>= putStrLn
становится:
main :: IO ()
main = putStr "hello" and then,
do nothing, but the result of doing nothing is " world"
and then, using the result of the previous action, putStrLn
В конце концов, нет ничего волшебного в IO. Он точно такой же магический, как точка с запятой в C.