Композиция функций в монаде IO - PullRequest
1 голос
/ 07 ноября 2019

Функция lines в Haskell разделяет строки строки в список строк:

lines :: String -> [String]

Функция readFile считывает файл в строку:

readFile :: FilePath -> IO String

Попытка составить эти функции, чтобы получить список строк в файле, приводит к ошибке типа:

Prelude> (lines . readFile) "quux.txt"
<interactive>:26:10: error:
    • Couldn't match type ‘IO String’ with ‘[Char]’
      Expected type: FilePath -> String
        Actual type: FilePath -> IO String
    • In the second argument of ‘(.)’, namely ‘readFile’
      In the expression: lines . readFile
      In the expression: (lines . readFile) "quux.txt"

Как я могу сделать трюк с монадой здесь?

Ответы [ 2 ]

6 голосов
/ 07 ноября 2019

Вы не можете составить их, по крайней мере, не с одним (.). Вы можете использовать fmap (или его операторную версию <$>), однако:

lines <$> readFile "quux.txt"  -- Produces IO [String], not [String]

Один из способов выразить это в виде некой композиции - сначала создать стрелку Клейсли (функциюнаберите a -> m b для некоторой монады m) из lines:

-- return . lines itself has type Monad m => String -> m [String]
-- but for our use case we can restrict the type to the monad
-- we are actually interested in.
kleisliLines :: String -> IO [String]
kleisliLines = return . lines

Теперь вы можете использовать оператор композиции Клейсли >=> для объединения readFile (сама стрелка Клейсли) и lines:

import Control.Monad  -- where (>=>) is defined

-- (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
-- Here, m ~ IO
--       a -> FilePath
--       b -> String
--       c -> [String]
(readFile >=> kleisliLines) "quux.txt"

Сравните это с оператором >>=, который требует, чтобы вы указали имя файла для readFile перед передачей результата в return . lines:

-- m >>= return . f === fmap f m === f <$> m
readFile "quux.txt" >>= kleisliLines

>=> естественно, если вы уже думаете о конвейере в терминах >=;если вы хотите что-то, что сохраняет порядок ., используйте <=< (также определено в Control.Monad, как (<=<) = flip (>=>); операнды просто меняются местами).

(kleisliLines <=< readFile) "quux.txt"
1 голос
/ 07 ноября 2019

Другие ответы, которые были даны до сих пор, заключались в том, чтобы заставить lines создать пустой монадический контекст, а затем использовать монадическую композицию (<=<), чтобы создать его с readFile. Но вы также можете пойти в другом направлении: поднимите lines, чтобы использовать монадический аргумент, а затем используйте обычную композицию, чтобы объединить ее с readFile:

(fmap lines . readFile) "quux.txt"

Конечно, если вы собираетесьчтобы сразу применить это к аргументу, проще написать

lines <$> readFile "quux.txt"
...