Почему тип соответствует следующей строке, а не той же строке в блоке `do`?
Я читаю несколько строк из ввода, которое должен ввести пользователь:
main :: IO ()
main = do
let size = 3
arr <- replicateM size getLine
let pairs = map parsePair arr
print pairs
Почему мне разрешено делать map parsePair arr
в отдельной строке, но не в той же строке, например:
arr <- map parsePair (replicateM size getLine)
При этом я получаю ошибку:
• Couldn't match type ‘[]’ with ‘IO’
Expected type: IO [Int]
Actual type: [[Int]]
Чтобы дать вам более подробную информацию, здесь parsePair
:
parsePair string = map parseInt $ words string
parseInt :: String -> Int
parseInt s = read s :: Int
2 ответа
Потому что тип replicateM size getLine
является IO [String]
это не список String
с, это в основном описание IO
действие, которое получит [String]
, Вы можете увидеть стрелку <-
в IO
монада как способ получить его и распаковать результат.
Вы можете, однако, сделать некоторую обработку на этом, так как IO
это Functor
также вы можете использовать fmap :: Functor f => (a -> b) -> f a -> f b
:
main :: IO [Int]
main = do
let size = 3
fmap (map parsePair) (replicateM size getLine)
или вы можете сдвинуть fmap
к getLine
часть:
main :: IO [Int]
main = do
let size = 3
replicateM size (fmap parsePair getLine)
Обратите внимание, что есть readLn :: Read a => IO a
функция, которая в основном fmap read getLine
(за исключением того, что он выполняет дополнительную обработку ошибок). Таким образом, мы можем использовать:
main :: IO [Int]
main = do
let size = 3
replicateM size readLn
Обратите внимание, что do
синтаксис с <-
Символ действительно не похож на оператор присваивания (который =
на многих языках, иногда :=
а иногда действительно <-
например, в R). Скорее, это специальная конструкция, которая выполняет монадическое действие и извлекает результат этого действия. Существует фундаментальное различие между действием и его результатом; и часто цитируемая аналогия заключается в том, что действие похоже на рецепт торта, а результат - сам торт.
применение map parsePair
прямо к IO
действие было бы подобно тому, как взять нож, чтобы разрезать рецепт на куски, и ожидать, что вы сможете использовать этот рецепт для выпечки готового пирога. Понятно, что это не так.
Точно так же вам сначала нужно выполнить (связать) IO
действие, прежде чем вы сможете манипулировать результатом. Вот что происходит на линии arr <- replicateM size getLine
: действие выполняется и в нем сохраняется только его результат arr
, чтобы потом можно было написать map parsePair arr
,
Кроме того, вы можете использовать fmap
оператор. По сути, он берет рецепт и некоторую инструкцию, что делать с результатом, а затем добавляет эту инструкцию в конец рецепта. Если вы затем выполните этот рецепт, результатом будет действительно торт, нарезанный на кусочки.