Функции привязки, которые принимают несколько аргументов

После прочтения некоторых очень простых haskell теперь я знаю, как "цепочки" монадических действий с помощью bind, например:

echo = getLine >>= putStrLn
Оператор

(>>=) очень удобен таким образом, но что, если я хочу связать монадические действия (или функторы), которые принимают несколько аргументов?

Учитывая, что (>>=) :: m a -> (a -> m b) -> m b кажется, что (>>=) может предоставить только один аргумент.

Например, writeFile принимает два аргумента (a FilePath и содержимое). Предположим, что у меня есть монадическое действие, которое возвращает a FilePath, и другое действие, которое возвращает String для записи. Как я могу объединить их с writeFile, не используя do -notation, но в общем?

Есть ли какая-либо функция с типом: m a -> m b -> (a -> b -> m c) -> m c, которая может это сделать?

Ответ 1

TL; ДР:

writeFile <$> getFilename <*> getString >>= id   :: IO ()

Моноды являются аппликативными

Так как ghc 7.10 каждая Монада (включая IO) также является аппликативной, но даже до этого вы можете сделать аппликатор из любой Монады, используя эквивалент

import Control.Applicative -- not needed for ghc >= 7.10

instance Applicative M where
  pure x = return x
  mf <*> mx = do
    f <- mf
    x <- mx
    return (f x)

И, конечно, IO является функтором, но Control.Applicative дает вам <$>, который можно определить как f <$> mx = fmap f mx.

Использование Применить для использования функций, которые принимают как можно больше аргументов

<$> и <*> позволяют использовать чистые функции f по аргументам, созданным аппликативным/монадическим вычислением, поэтому, если f :: String -> String -> Bool и getFileName, getString :: IO String, то

f <$> getFileName <*> getString :: IO Bool

Аналогично, если g :: String -> String -> String -> Int, то

g <$> getString <*> getString <*> getString :: IO Int

От IO (IO ()) до IO ()

Это означает, что

writeFile <$> getFilename <*> getString :: IO (IO ())

но вам нужно что-то типа IO (), а не IO (IO ()), поэтому нам нужно либо использовать join :: Monad m => m (m a) -> m a, как в Xeo comment, либо нам нужно чтобы взять монадический результат и запустить его, т.е. тип (IO ()) -> IO (), связать его с. Тогда это будет id, поэтому мы можем либо сделать

join $ writeFile <$> getFilename <*> getString :: IO ()

или

writeFile <$> getFilename <*> getString >>= id :: IO ()

Ответ 2

Для этого гораздо проще использовать обозначение do, а не запрашивать комбинатор

action1 :: MyMonad a
action2 :: MyMonad b
f :: a -> b -> MyMonad c

do
    x <- action1
    y <- action2
    f x y

Ответ 3

Это typechecks:

import System.IO

filepath :: IO FilePath
filepath = undefined

someString :: IO String
someString = undefined

testfun = filepath   >>= (\fp -> 
          someString >>= (\str -> 
          writeFile fp str  ))

Но я чувствую, что использование обозначений более читаемо.