Преобразовать нотацию do с более чем двумя действиями, чтобы использовать функцию bind - PullRequest
6 голосов
/ 14 сентября 2010

Я знаю, что следующая функция "bind" для записи "do" эквивалентна getLine >>= \line -> putStrLn

do line <- getLine
   putStrLn line

Но как следующие обозначения эквивалентны функции связывания?

do line1 <- getLine
   putStrLn "enter second line"
   line2 <- getLine
   return (line1,line2)

Ответы [ 4 ]

16 голосов
/ 14 сентября 2010

Я так понимаю, вы пытаетесь увидеть, как связать результат "putStrLn". Ответ в типе putStrLn:

putStrLn :: String -> IO ()

Помните, что "()" - это тип единицы измерения, который имеет одно значение (также пишется "()"). Таким образом, вы можете связать это точно так же. Но так как вы не используете его, вы привязываете его к значению «пофиг»:

getLine >>= \line1 ->
putStrLn "enter second line" >>= \_ ->
getline >>= \line2 ->
return (line1, line2)

Как это происходит, уже определен оператор для игнорирования возвращаемого значения, ">>". Так что вы можете просто переписать это как

getLine >>= \line1 ->
putStrLn "enter second line" >>
getline >>= \line2 ->
return (line1, line2)

Я не уверен, пытаетесь ли вы также понять, как операторы связывания связаны последовательно. Чтобы увидеть это, позвольте мне поставить неявные скобки и дополнительные отступы в приведенном выше примере:

getLine >>= (\line1 ->
   putStrLn "enter second line" >> (
      getline >>= (\line2 ->
         return (line1, line2))))

Каждый оператор связывания связывает значение слева с функцией справа. Эта функция состоит из всех остальных строк в предложении «do». Таким образом, переменная, связанная через лямбду («line1» в первой строке), находится в области действия для всего остального предложения.

7 голосов
/ 14 сентября 2010

В этом конкретном примере вы можете на самом деле избежать do и >>=, используя комбинаторы из Control.Applicative:

module Main where
import Control.Applicative ((<$>), (<*>), (<*))

getInput :: IO (String, String)
getInput = (,) <$> getLine <* putStrLn "enter second line" <*> getLine

main = print =<< getInput

Что работает, как и ожидалось:

travis@sidmouth% ./Main
hello
enter second line
world
("hello","world")

Поначалу это выглядит немного странно, но, на мой взгляд, аппликативный стиль становится очень естественным, когда вы к нему привыкли.

3 голосов
/ 15 сентября 2010

Я бы настоятельно рекомендовал вам прочитать главу Расслабление Do-блоков в книге Хаскелл реального мира.Это говорит вам, что вы все не правы.Для программиста это естественный способ использования лямбды, но do-блок реализован с использованием функций, которые - в случае сбоя обработки образца - вызовут реализацию fail соответствующей монады.

ДляНапример, ваш случай выглядит так:

let f x =
        putStrLn "enter second line" >>
        let g y = return (x,y)
            g _ = fail "Pattern mismatched"
        in getLine >>= g
    f _ = fail "Pattern mismatched"
in getLine >>= f

В таком случае это может быть совершенно неактуально.Но рассмотрим некоторое выражение, которое включает в себя сопоставление с образцом.Кроме того, вы можете использовать этот эффект для некоторых специальных вещей, например, вы можете сделать что-то вроде этого:

oddFunction :: Integral a => [a] -> [a]
oddFunctiond list = do
  (True,y) <- zip (map odd list) list
  return y

Что будет делать эта функция?Вы можете прочитать это утверждение как правило для работы с элементами списка.Первая инструкция связывает элемент списка с переменной y, но только если y нечетно.Если у четный, происходит сбой сопоставления с образцом и вызывается fail.В экземпляре монады для списков fail - это просто [].Таким образом, функция удаляет все четные элементы из списка.

(я знаю, oddFunction = filter odd сделает это лучше, но это только пример)

3 голосов
/ 14 сентября 2010
getLine >>= \line1 ->
putStrLn "enter second line" >>
getLine >>= \line2 ->
return (line1, line2)

Обычно foo <- bar становится bar >>= \foo ->, а baz становится baz >> (если это не последняя строка do-блока, в этом случае он просто остается baz).

...