I am having trouble wrapping my head around making to work a conversion of a list into a monadic function that uses values of the list.
For example, I have a list [("dir1/content1", "1"), ("dir1/content11", "11"), ("dir2/content2", "2"), ("dir2/content21", "21")]
that I want to be converted into a monadic function that is mapped to a following do
statement:
do
mkBlob ("dir1/content1", "1")
mkBlob ("dir1/content11", "11")
mkBlob ("dir2/content2", "2")
mkBlob ("dir2/content21", "21")
I imagine it to be a function similar to this:
contentToTree [] = return
contentToTree (x:xs) = (mkBlob x) =<< (contentToTree xs)
But this does not work, failing with an error:
• Couldn't match expected type ‘() -> TreeT LgRepo m ()’
with actual type ‘TreeT LgRepo m ()’
• Possible cause: ‘(>>=)’ is applied to too many arguments
In the expression: (mkBlob x) >>= (contentToTree xs)
In an equation for ‘contentToTree’:
contentToTree (x : xs) = (mkBlob x) >>= (contentToTree xs)
• Relevant bindings include
contentToTree :: [(TreeFilePath, String)] -> () -> TreeT LgRepo m ()
I do not quite understand how to make it work.
Here is my relevant code:
import Data.Either
import Git
import Data.Map
import Conduit
import qualified Data.List as L
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as BL
import Control.Monad (join)
type FileName = String
data Content = Content {
content :: Either (Map FileName Content) String
} deriving (Eq, Show)
contentToPaths :: String -> Content -> [(TreeFilePath, String)]
contentToPaths path (Content content) = case content of
Left m -> join $ L.map (\(k, v) -> (contentToPaths (if L.null path then k else path "/" k) v)) $ Data.Map.toList m
Right c -> [(BS.pack path, c)]
mkBlob :: MonadGit r m => (TreeFilePath, String) -> TreeT r m ()
mkBlob (path, content) = putBlob path
=<< lift (createBlob $ BlobStream $
sourceLazy $ BL.fromChunks [BS.pack content])
sampleContent = Content $ Left $ fromList [
("dir1", Content $ Left $ fromList [
("content1", Content $ Right "1"),
("content11", Content $ Right "11")
]),
("dir2", Content $ Left $ fromList [
("content2", Content $ Right "2"),
("content21", Content $ Right "21")
])
]
Would be grateful for any tips or help.
CodePudding user response:
You have:
- A list of values of some type
a
(in this casea ~ (String, String)
). So,xs :: [a]
- A function
f
froma
to some typeb
in a monadic context,m b
. Since you're ignoring the return value, we can imagineb ~ ()
. So,f :: Monad m => a -> m ()
.
You want to perform the operation, yielding some monadic context and an unimportant value, m ()
. So overall, we want some function doStuffWithList :: Monad m => [a] -> (a -> m ()) -> m ()
. We can search Hoogle for this type, and it yields some results. Unfortunately, as we've chosen to order the arguments, the first several results are little-used functions from other packages. If you scroll further, you start to find stuff in base
- very promising. As it turns out, the function you are looking for is traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()
. With that, we can replace your do-block with just:
traverse_ mkBlob [ ("dir1/content1", "1")
, ("dir1/content11", "11")
, ("dir2/content2", "2")
, ("dir2/content21", "21")
]
As it happens there are many names for this function, some for historical reasons and some for stylistic reasons. mapM_
, forM_
, and for_
are all the same and all in base
, so you could use any of these. But the M_
versions are out of favor these days because really you only need Applicative
, not Monad
; and the for
versions take their arguments in an order that's convenient for lambdas but inconvenient for named functions. So, traverse_
is the one I'd suggest.
CodePudding user response:
Assuming mkBlob
is a function that looks like
mkBlob :: (String, String) -> M ()
where M
is some specific monad, then you have the list
xs = [("dir1/content1", "1"), ("dir1/content11", "11"), ("dir2/content2", "2"), ("dir2/content21", "21")]
whose type is xs :: [(String, String)]
. The first thing we need is to run the mkBlob
function on each element, i.e. via map
.
map mkBlob xs :: [M ()]
Now, we have a list of monadic actions, so we can use sequence
to run them in sequence.
sequence (map mkBlob xs) :: M [()]
The resulting [()]
value is all but useless, so we can use void
to get rid of it
void . sequence . map mkBlob $ xs :: M ()
Now, void . sequence
is called sequence_
in Haskell (since this pattern is fairly common), and sequence . map
is called mapM
. Putting the two together, the function you want is called mapM_
.
mapM_ mkBlob xs :: M ()