Binding functions that take multiple arguments

℡╲_俬逩灬. 提交于 2019-11-29 16:56:24

问题


After reading some very basic haskell now I know how to "chain" monadic actions using bind, like:

echo = getLine >>= putStrLn

(>>=) operator is very handy in this fashion, but what if I want to chain monadic actions (or functors) that take multiple arguments?

Given that (>>=) :: m a -> (a -> m b) -> m b it seems like (>>=) can supply only one argument.

For example, writeFile takes two arguments (a FilePath and the contents). Suppose I have a monadic action that returns a FilePath, and another action that returns a String to write. How can I combine them with writeFile, without using the do-notation, but in a general way?

Is there any function with the type: m a -> m b -> (a -> b -> m c) -> m c that can do this?


回答1:


TL;DR:

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

Monads are Applicative

Since ghc 7.10 every Monad (including IO) is also an Applicative, but even before that, you could make an Applicative out of any Monad using the equivalent of

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)

And of course IO is a functor, but Control.Applicative gives you <$> which can be defined as f <$> mx = fmap f mx.

Use Applicative to use functions that take as many arguments as you like

<$> and <*> let you use pure functions f over arguments produced by an Applicative/Monadic computation, so if f :: String -> String -> Bool and getFileName, getString :: IO String then

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

Similarly, if g :: String -> String -> String -> Int, then

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

From IO (IO ()) to IO ()

That means that

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

but you need something of type IO (), not IO (IO ()), so we need to either use join :: Monad m => m (m a) -> m a as in Xeo's comment, or we need a function to take the monadic result and run it, i.e. of type (IO ()) -> IO () to bind it with. That would be id then, so we can either do

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

or

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



回答2:


It's much easier to use do notation for this, rather than asking for a combinator

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

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



回答3:


This typechecks:

import System.IO

filepath :: IO FilePath
filepath = undefined

someString :: IO String
someString = undefined

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

But I feel using do notation is more readable.



来源:https://stackoverflow.com/questions/22115472/binding-functions-that-take-multiple-arguments

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!