Вы не можете составить их, по крайней мере, не с одним (.)
. Вы можете использовать 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"