Вопрос в том, что вы хотите сделать со значением String
, к которому приведет ваше действие IO String
при запуске программы. Вы пишете так, как будто у вас уже есть руки на веревке, но у вас нет.
Или: вы пишете так, как будто вы определили или изолировали строку, но вы этого не сделали. Итак, вы определили или изолировали действие , которое возвращает строку . Отдельные случаи выполнения этого действия будут возвращать разные строки.
Предположительно, вы пытаетесь определить более сложное действие - что-то типа IO Blah
- возможно, что-то типа IO ()
типа чего-то, что может быть скомпилировано в исполняемый файл. Идея состоит в том, что в более сложном действии, получив значение String
, выполнив действие типа IO String
- действие, которое вы до сих пор определили - исполнитель сложного действия продолжит выполнять то, что зависит только от того, что это значение. Это что-то, представленное функцией типа String -> IO Blah
- возможно String -> IO ()
Конечно, такая функция не принимает значения IO String
(то есть действия, которые возвращают строки) в качестве аргументов, а String
значения в качестве аргументов. Мы не можем присоединиться к ним напрямую.
Чтобы перейти от вашего действия , которое возвращает строку - ваше значение IO String
- и функцию 'String -> IO Blah', к чему-то новому - действие , которое возвращает бла - мы присоединяемся к ним через функцию >>=
. Специально для этого случая >>=
имеет тип IO String -> (String -> IO Blah) -> IO Blah
- это может быть IO String -> (String -> IO ()) -> IO ()
Итак, чтобы взять самый тривиальный пример пары таких вещей, рассмотрим getLine
и putStrLn
.
getLine
- это действие по определению, какая строка была только что введена - она имеет тип IO String
.
Можно сказать, что putStrLn
выводит значение String
на экран, а затем возвращает к левому полю . Но это поверхностно: то, что определенное действие будет сделано, зависит от спецификации значения String
, поэтому оно имеет тип String -> IO()
. То есть: putStrLn
ничего не делает, это функция, которая отображает строки на вещи, которые могут быть выполнены. Таким образом, как обычно, вы можете определить значение в его типе диапазона (действия, то есть IO ()
s), следуя за знаком функции с именем чего-либо в типе домена (строки, т.е. String
). Таким образом, комбинация putStrLn "Gee whiz"
называет определенное действие, что-то типа IO ()
.
ghci
будет выполнять такие действия на лету, поэтому для любой строки, такой как «Gee whiz», вы можете написать putStrLn "Gee whiz"
, и она немедленно «выполнит это действие» - действие, написав «Gee whiz» для экран и возвращаясь к левому полю.
Prelude> putStrLn "Gee whiz"
Gee whiz
Prelude>
Аналогично, односимвольная строка, в которой есть только символ Unix \BEl
, - это строка, которую мы называем '\BEl':[]
или ['\BEL']
или "\BEL"
. Для этой строки в качестве аргумента putStrLn
имеет слышимый совершенно другой вид действия для значения. Мы получаем
* * 1068
Здесь вы услышите звонок Unix, прежде чем он вернется к левому полю. Это довольно слабая аудиопрограмма, но вы здесь. ghci
означает выполнение действия звучания звонка Unix , действия, которое вы назвали словами putStrLn "\BEL"
перед нажатием возврата.
Так или иначе, getLine
- это значение типа IO String
, и вы хотите «взять это строковое значение из IO». Конечно, он еще не существует, это зависит от того, что пользователь вводит. Но мы можем рассмотреть, что программа должна делать с таким значением, когда оно ее получает. Мы можем указать это, указав функцию от строки до действия, например putStrLn
. Таким образом, мы можем определить полное действие, которое «принимает значение» и использует его определенным образом, составляя их с >>=
или do
нотацией сахара.
Самым тривиальным случаем будет что-то вроде echo
:
echo :: IO ()
echo = getLine >>= putStrLn
или эквивалентно
echo = getLine >>= (\x -> putStrLn x)
или do
: 1091 *
echo = do
the_string_i_want_to_take <- getLine
putStrLn the_string_i_want_to_take
или менее нелепо:
echo = do
x <- getLine
putStrLn x
Конечно, вы хотите "взять строку" и, возможно, возиться с ней до окончания.
reverseEcho :: IO ()
reverseEcho = getLine >>= (\x -> putStrLn (reverse x))
или более компактно:
reverseEcho = getLine >>= (putStrLn . reverse)
или в do
нотации:
reverseEcho = do
the_string_i_want_to_take <- getLine
putStrLn (reverse the_string_i_want_to_take)
или менее нелепо:
reverseEcho = do
x <- getLine
putStrLn (reverse x)
если вы хотите думать о «обращении строки» как о чем-то, что делается со строкой между получением и печатью, вы можете написать:
reverseEcho = do
the_string_i_want_to_take <- getLine
the_string_after_i_have_processed_it <- return (reverse the_string_i_want_to_take)
putStrLn (the_string_after_i_have_processed_it)
или
reverseEcho = do
x <- getLine
y <- return x
putStrLn y
или эквивалентно
reverseEcho = (getLine >>= (return . reverse)) >>= putStrLn
Здесь круглые скобки не нужны, потому что уровни приоритета для .
и >>=
соответственно оптимизированы. Но (getLine >>= (return . reverse))
- это просто другое имя «действия, которое возвращает строку», значение IO String
, которое не является самой строкой. Вы не можете применить функцию String -> Whatever
непосредственно к ней, чтобы получить Whatever
, но вы можете комбинировать ее с функцией от строк до действий через >>=
.
Аналогично
reverseFileAA :: IO ()
reverseFileAA = readFile "AA.txt" >>= writeFile "reversedAA.txt" . reverse
- это действие записи файла с именем "reversedAA.txt" с обратной строкой, найденной в AA.txt
, какой бы она ни была, и может быть записана
reverseFileAA = do
old_file_contents <- readFile "AA.txt"
new_file_contents <- return (reverse old_file_contents)
writeFile "reversedAA.txt" old_file_contents