0

I am trying to create a function that 'extracts' and evaluate the value of a each monad in a list of monads such that a final value - accumulated or so - can be returned. The signature for such a function would be something like accCurryingMonad :: [SomeCurryingMonad a d] -> SomeCurryingMonad a d. To make this harder the value of each monad is a tupple. Furthermore if one of the values evaluated 'fails' or returns 'nothing' the function is still to proceed with the hitherto accumulated value, and use it for the rest of the chain of monadic operations in the list.

For the purpose of solving my problem I have had a re-read of the State s a monad as described in LYAH Stacking Manip, and have tried to rewrite the code of the 'Stack and stones' problem to fit my purpose, since I suspect that this is a problem that can be solved with foldM.

Heres the rewritten MRE code:

import Control.Monad.State

type Stack = [Int]

pop :: State Stack Int
pop = do
    stack <- get
    case stack of
        (x:xs) -> do
            put xs
            return x
        _ -> error "Empty stack"

push :: Int -> State Stack ()
push a = do
    stack <- get
    put (a:stack)

-- Perform a single step of the computation using State monad
singleStep :: Int -> State Stack Int
singleStep value = do
    push value    
    pop

-- Perform the computation 10 times using foldM with State monad
stackManip10Times :: State Stack Int
stackManip10Times = foldM (\_x v -> v) 0 [singleStep 10, singleStep 20, singleStep 30]

main :: IO ()
main = do
    let initialStack = [200, 300, 400, 500] :: Stack
        (result, finalStack) = runState stackManip10Times initialStack
    putStrLn $ "Result: " ++ show result
    putStrLn $ "Final Stack: " ++ show finalStack

How can I use the foldM (if that is the right approach, that is) to turn:

stackManip10Times = foldM (\_x v -> v) 0 [singleStep 10, singleStep 20, singleStep 30]

into something like this function which doesn't take a generic function (e. g singleStep v), and yet evaluates and manipulate the value within the monad, and returns the result of all of the manipulations no matter what monadic operation, within a single monad:

stackManip10Times :: State Stack Int
stackManip10Times = foldM (\_x v -> v) () [push 10, pop, push 20]
Piskator
  • 605
  • 1
  • 9
  • 2
    `push 10` has type `State Stack ()` and `pop` is `State Stack Int`, how do you plan to put them in the same list? – n. m. could be an AI Aug 02 '23 at 06:32
  • @n.m.couldbeanAI , I don't know! And this might perhaps be what is confusing me a bit. The push and the pop, are as I stated just some rewritten code that I thought would suit my needs. The code I want to create however is meant to take some of the original Monad-operators like `return` or `>>=`-operator and apply on each instance of the monad for `n` number of monads, but returning the value of the monad. Had I had only 2 monads, I guess I could do it with the bind operator on each, and use an if clause to proceed if the chain would fail. Here the list could be infinite. – Piskator Aug 02 '23 at 06:45
  • 2
    It looks like your simplified example doesn't represent your real problem. – n. m. could be an AI Aug 02 '23 at 06:48
  • @n.m.couldbeanAI, yes maybe that is the case. I will have to try to rethink the problem, such that it represents the real problem. I think what I just described, and what I wrote in the beginning of the thread is probably the closest I get though; It has to do with applying a chain of monadic operations for an unknown sized list of monads, and return manipulate it such that side-effects as well as returned value are kept, even when the result is 'Nothing' (using maybe monad as an illustrative example). I emphasized the tupple-value to begin with to allude to the side-effects vs. return-val. – Piskator Aug 02 '23 at 07:36
  • I think the asnwer on stackoverflow that comes closest to what I try to accomplish is the one with `sequence` given here: https://stackoverflow.com/questions/75723109/accumulate-a-function-over-a-list-of-monads-maybe The problem with this answer is that the function created with `sequence` combined with `fmap` 'fails' (returns `Nothing`) if the list contains a single `Nothing`. Here mine is supposed to return the manipulated value, within a `Just value`. – Piskator Aug 02 '23 at 07:43
  • `Nothing` is a value and it is no better or worse than `Just 42` as far as `sequence` is concerned. It doesn't know anything about specific monads. Do you want the last value that is not a failure (only for `MonadFail` instances, not for arbitrary monads)? I don't think there is a standard function for that but it should be easy to write one. – n. m. could be an AI Aug 02 '23 at 08:05
  • Or perhaps the last non-error for a MonadError? – n. m. could be an AI Aug 02 '23 at 08:13
  • @n.m.couldbeanAI , I am still rather new to Haskell, so some of the 'jargon' still escapes me a bit, but I try to the best of my ability to acquire the concepts and understand them the way that you veterans in here understand them. But you will probably still have to bear with me for not fully grasping every concept. I am not sure what you mean by: _'no better or worse'_, nor am I sure what you mean by _It doesn't know anything about specific monads_. Nor am I sure how `MonadFail` relates to _arbitrary monads_ either. but.... – Piskator Aug 02 '23 at 08:15
  • what I want to accomplish is to return a value (that of the accumulated `foldM`) for an unknown sized list. Where every 'failing'/aboned monad doesn't effect the returned value, but might have side-effects. And where all of the 'returned' values can be manipulated and accumulates. I am reading [side-effects](https://stackoverflow.com/questions/33386622/what-exactly-does-effectful-mean/33398273#33398273), and they mention something about Monads, applicatives and Functors all having different purposes. I don't know if this is what you mean in the above. – Piskator Aug 02 '23 at 08:20
  • So in other words. I want the 'successful' monads to manipulate the return values and apply side-effects, and the 'failing'/abandoned (the ones that have abandoned the computation, which is different from the `MonadFail`, as far as I understnad it), to skip their computation, although they still apply the side-effect. Such that if the return value is the value `a` of `(a, log)` within a monad the `a` will not get manipulated in the 'failing/abandoned' one, althought the `log`, which is the side effect might be manipulated for the coming monadical operations. – Piskator Aug 02 '23 at 09:05
  • You better give words like "failing" or "successful" or "abandoned" some kind of meaning. Right now I have no idea what they could possibly mean. "skip their computation, although they still apply the side-effect." doesn't parse. Perhaps you have some concrete example in mind? It is always useful to consider a concrete example before going abstract. – n. m. could be an AI Aug 02 '23 at 09:44
  • 1
    It's not easy to understand the question, but would something like [partitionEithers](https://hackage.haskell.org/package/base/docs/Data-Either.html#v:partitionEithers) be of use? – Mark Seemann Aug 02 '23 at 11:08
  • @n.m.couldbeanAI yes, I think my wording is similar to what how they adress the maby-monad in the link I posted in the comments above, where they mention 'abort computation' with regards to nothing. I was in the pr oces of rewriting my post but then my computer crashed, nad now you responded with something close to what I asked for I think. – Piskator Aug 02 '23 at 11:56
  • @MarkSeemann ,yes, this is defintely useful as well. I am aware as you can see fromo the comments, that the problem might be formulated in a better way, but I might still lack the concapets and vocabulary to fully express it. – Piskator Aug 02 '23 at 11:58

1 Answers1

3

This is how I interpret the question, I may well be grossly mistaken.

Let's look at the implementation of sequence (for lists, not for generic Traversables).

sequence = mapM id

mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM f as = foldr k (return []) as
            where
              k a r = do { x <- f a; xs <- r; return (x:xs) }

This has no provision for catching and discarding errors, so let's add it.

sequenceNF :: MonadError e m => [m a] -> m [a]
sequenceNF = mapMNF id

mapMNF :: MonadError e m => (a -> m b) -> [a] -> m [b]
mapMNF f as = foldr k (return []) as
            where k a r = do { 
               x <- f a; xs <- r; return (x:xs) 
            } `catchError` (\_ -> r)

Now we can:

sequenceNF [Just 42, Nothing, Just 13] 
-- => Just [42, 13]

sequenceNF [print 42, ioError (userError "Oops"), print "Hi"] 
-- => IO [(), ()]
-- will print 42 and "Hi" when executed 

sequenceNF [
   print "abc",
   do { print 42; print "def" },
   do { print "Hi"; ioError (userError "Oops"); print "Bye"; },
   print "Ok" ]
-- => IO [(), (), ()]
-- will print: "abc" 42 "def" "Hi" "Ok"
-- note there are 4 elements in the input list
-- but only 3 elements in the output list
-- execution of the third element failed, so no value collected
-- although side effects of it are still executed
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • This seems about right, although a bit more complex than I thought. Considering that I thought one could use a generic function like `foldM` and put an `if`-clause within the function that `foldM` takes. – Piskator Aug 02 '23 at 11:48
  • I guess from the point of `sequenceNF` you could then extract the list and apply it foldl to that? – Piskator Aug 02 '23 at 11:50
  • What is the difference between a list and a _generic Traversable_ as you mention in the post? – Piskator Aug 02 '23 at 11:52
  • 2
    `sequence` for the traversable has no idea about the shape of the thing it traverses. It works element-wise and must return the same exact shape it receives. It cannot skip anything because the shape may not allow skipping (think binary tree or a fixed-size array. – n. m. could be an AI Aug 02 '23 at 11:56
  • Ok, I think I understand. Thank you. – Piskator Aug 02 '23 at 11:59
  • For the normal `sequence` without the `mapMNF` the `Hi` is the last one printed in `ghci` before an `*** Exception: user error (Oops)`, not `Ok`. I am not entirely sure why this is the case, or why it should print `Ok` as you suggested, for that matter. You use `do`-expressions with nesting, but wouldn't they still fail if one of them fail? – Piskator Aug 02 '23 at 12:12
  • @Piskator sorry it's just a typo, I meant sequenceNF in the last case too (fixed). For the regular `sequence` it behaves like you describe. – n. m. could be an AI Aug 02 '23 at 12:21
  • Ok, and thank you for the effort you put into answering and figuring out what I meant with the question despite my poor ability to express it. It is much appreciated. I need to play around abit to understand your suggested solution. – Piskator Aug 02 '23 at 12:31