Если вы обнаружите, что запись do
сбивает с толку, я бы порекомендовал не использовать ее вообще.С помощью >>=
вы можете делать все, что вам нужно.Просто представьте, что его тип
(>>=) :: IO a -> (a -> IO b) -> IO b
Тем не менее, давайте посмотрим на ваш код.
let
в блоке do
дает имя некоторому значению.Это то же самое, что и за пределами do
, поэтому здесь это бесполезно (это не дает вам дополнительных возможностей).
<-
более интересно: оно действует как «извлечение значения из IO локально»."construct (если вы немного щуритесь).
hoo :: [String] -> IO [String]
hoo (xs:ys:yss) = do
-- The right-hand side (goo xs ys) has type IO [String], ...
rs <- goo xs ys
-- ... so rs :: [String].
-- We can apply the same construct to our recursive call:
hs <- hoo yss
-- hoo yss :: IO [String], so hs :: [String].
let gs = rs ++ hs
return gs
Как упоминалось выше, let
просто привязывает имя к значению, поэтому оно нам здесь не нужно:
hoo :: [String] -> IO [String]
hoo (xs:ys:yss) = do
rs <- goo xs ys
hs <- hoo yss
return (rs ++ hs)
В качестве альтернативы, без обозначений do
и <-
мы бы сделали это следующим образом.
(>>=) :: IO a -> (a -> IO b) -> IO b
>>=
принимает значение IO
и функцию обратного вызова, и этозапускает функцию с использованием развернутого значения (a
).Это означает, что внутри функции мы получаем локальный доступ к значению, пока результат всего этого снова равен IO b
(для некоторого произвольного типа b
).
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys -- :: IO [String]
...
У нас есть IO [String]
и нам нужно что-то сделать с [String]
, поэтому мы используем >>=
:
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys >>= (\rs -> ...)
Если вы посмотрите на сигнатуру >>=
, роль a
играет [String]
здесь (rs :: [String]
) и b
также [String]
(потому что hoo
в целом необходимо вернуть IO [String]
).
Так что же нам делать в части ...
?Нам нужно сделать рекурсивный вызов hoo
, что снова приводит к значению IO [String]
, поэтому мы снова используем >>=
:
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys >>= (\rs -> hoo yss >>= (\hs -> ...))
Опять, hs :: [String]
и ...
лучше иметьнаберите IO [String]
, чтобы все проверить:
Теперь, когда у нас есть rs :: [String]
и hs :: [String]
, мы можем просто объединить их:
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys >>= (\rs -> hoo yss >>= (\hs -> rs ++ hs)) -- !
Это ошибка типа.rs ++ hs :: [String]
, но контекст требует IO [String]
.К счастью, есть функция, которая может помочь нам:
return :: a -> IO a
Теперь она проверяет:), большинство паренсов здесь на самом деле необязательны:
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys >>= \rs -> hoo yss >>= \hs -> return (rs ++ hs)
И с небольшим переформатированием все это может выглядеть довольно внушительно:
hoo :: [String] -> IO [String]
hoo (xs : ys : yss) =
goo xs ys >>= \rs ->
hoo yss >>= \hs ->
return (rs ++ hs)