What recursion scheme should I use to repeat an effectful action until its result matches some criterion?

北城以北 提交于 2020-01-03 19:04:18

问题


That is, what I am asking about is a loop.

effectful :: Int -> IO Int
effectful n = do
    putStrLn $ "Effect: " ++ show n
    return n

condition = (== 3)

final :: Int -> IO ()
final n = putStrLn $ "Result: " ++ show n

loop = ?

It should work like this:

λ loop [1..10]
Effect: 1
Effect: 2
Effect: 3
Result: 3

I can offer a recursive definition:

loop (x: xs) = do
    r <- effectful x
    if condition r
       then final r
       else loop xs

However, I am having trouble representing this effect with any combination of Functor, Monad, Foldable and Traversable methods, because they always insist on evaluating all of the actions, while what I need is to stop at some point within the list.

For instance, with an unfoldrM (which is an effectful version of Data.List.unfoldr that I made up for the occasion) I can perform exactly the actions I need, but I cannot attain the value of the last action, because the function parameter returns Nothing:

unfoldrM :: Monad m => (a -> MaybeT m (b, a)) -> a -> m [b]
unfoldrM f x = fmap reverse $ unfoldrM' f x (return [ ])
  where
    -- unfoldrM' :: (a -> MaybeT m (b, a)) -> a -> m [b] -> m [b]
    unfoldrM' f x ys = runMaybeT (f x) >>= \r -> case r of
        Just (y, x') -> unfoldrM' f x' (fmap (y:) ys)
        Nothing      -> ys

f :: [Int] -> MaybeT IO (Int, [Int])
f (x: xs) = (lift . effectful $ x) >>= \y ->
    if condition y
       then MaybeT (return Nothing)
       else lift . return $ (y, xs)

— Which got me thinking: "What if I used Either instead, then unwrapped the Left result?" This line of consideration led me to Control.Monad.Except and then to the idea that I should consider the desired result to be the exception in the control flow.

exceptful :: Int -> ExceptT Int IO ()
exceptful n = do
    r <- lift (effectful n)
    if condition r
       then throwError r
       else return ()

loop' xs = fmap (fromRight ())
         $ runExceptT (traverse_ exceptful xs `catchError` (lift . final))

 

λ loop' [1..10]
Effect: 1
Effect: 2
Effect: 3
Result: 3

What I think about this solution is that it is awful. First, it is counter-intuitive to use the left side as the actual result carrier, second, this code is so much more complex than the recursive loop that I started with.

What can be done?


回答1:


I like to model these kinds of tasks as functions involving effectful streams. The streaming package is good for that, as it provides an api quite similar to that of conventional, pure lists. (That said, the Functor / Applicative / Monad instances are a bit different: they work by Stream "concatenation", not by exploring all combinations like in pure lists.)

For example:

import Streaming
import qualified Streaming.Prelude as S

loop :: Int -> (a -> Bool) -> IO a -> IO (Maybe a)
loop limit condition = S.head_ . S.filter condition . S.take limit . S.repeatM

Using the repeatM, take, filter and head_ functions from "streaming".

Or, if we have an effectful function and a list of values:

loop :: (b -> Bool) -> (a -> IO b) -> [a] -> IO (Maybe b)
loop condition effectful = S.head_ . S.filter condition . S.mapM effectful . S.each

Using each and mapM from "streaming".

If we want to perform a final effectful action:

loop :: (b -> IO ()) -> (b -> Bool) -> (a -> IO b) -> [a] -> IO ()
loop final condition effectful = 
    S.mapM_ final . S.take 1 . S.filter condition . S.mapM effectful . S.each 

Using mapM_ from "streaming".




回答2:


There is one base class that you are forgetting, my friend, and that is Alternative. Consider the following definitions:

loop :: Alternative m => [m Int] -> m Int
loop = foldr (<|>) empty

effectful' :: Int -> IO Int
effectful' n = effectful n <* if condition n then return () else empty

Now surely you can see where it is going:

λ loop (effectful' <$> [1..10]) >>= final
Effect: 1
Effect: 2
Effect: 3
Result: 3

You can even have an infinite list of alternatives here; if there is a guarantee that eventually one of them will not be empty, the whole loop is well-defined.



来源:https://stackoverflow.com/questions/57347510/what-recursion-scheme-should-i-use-to-repeat-an-effectful-action-until-its-resul

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