New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add monadic traversal with accumulator to Data.Traversable #65
Comments
If you change the argument order a bit, you'll see that import Control.Applicative
import Data.Traversable
import Control.Monad
import Data.Coerce
myMapAccumM
:: (Monad m, Traversable t)
=> (a -> s -> m (s, b))
-> t a -> s -> m (s, t b)
myMapAccumM f = runStateM #. mapM (StateM #. f)
newtype StateM s m a = StateM
{ runStateM :: s -> m (s, a) }
instance Monad m => Functor (StateM s m) where
fmap = liftM
instance Monad m => Applicative (StateM s m) where
pure a = StateM $ \s -> pure (s, a)
liftA2 = liftM2
(<*>) = ap
m *> n = StateM $ \s -> runStateM m s >>= \(s', _) -> runStateM n s'
instance Monad m => Monad (StateM s m) where
m >>= f = StateM $ \s -> runStateM m s >>= \(s', a) -> runStateM (f a) s'
(>>) = (*>)
-- Stolen from profunctors
(#.) :: Coercible b c => p b c -> (a -> b) -> a -> c
(#.) _ = coerce
{-# INLINE (#.) #-} Of course, you can define your |
I think Of course, your monad might already implementing |
I don't think that matters, does it? Unless I'm missing something, naked |
Indeed, type My = State Integer
f :: Char -> StateT String My Int
f c = do
modify (c:)
lift $ modify succ
return $ ord c
test :: (([Int], String), Integer)
test = mapM f "Hello, world!" `runStateT` "" `runState` 0 |
@lykahb could you please prepare a GHC merge request, so that we have a specific design to vote on? Does it fit into |
@Bodigrim Do you think it is better to split the voting into two parts? At first, vote whether we need In my opinion, |
Yes, we can have separate votes, but please put up a draft MR first, so that we know that there exists at least one working implementation of the proposal. |
Submitted a merge request https://gitlab.haskell.org/ghc/ghc/-/merge_requests/8384. |
Your decision to use a lazy state transformer needs explanation/justification. |
To be more explicit, I think this should probably use a strict state transformer, like |
I believe that the lazy versions of the functions are the default. And there is a convention that the lazy functions have a plain name, and the strict ones have a Do you suggest adding the strict versions or making the transformer strict by default? As for the details of the implementation I think that it is better to re-use an existing implementation of the state monad, rather than create a new one. So, the definitions are copied from transformers library. |
The strict state monad transformer doesn't force the state; it only forces the pairs. It should be thought of as the "default" state monad transformer. The lazy version is a weird beast. It doesn't strictly obey the monad laws, and it tends to break intuition about what gets calculated when. As a general rule, it should only be used when its special behavior is specifically desired. It would be valuable to consider actual applications for |
This is a convincing argument. The lazy variant seems to be more general, and it is the default in the transformers library. But for the use cases I've seen the laziness wouldn't be useful. Updated the MR to use the strict state transformer. |
I believe that it's the other way round: the strict variant is more general, in the sense that you can use the strict variant to implement a variant with the behaviour and space asymptotics of the lazy version, but not vice versa. EDIT: Having thought about it for a bit I suspect that neither can be used to implement the other. |
Since |
It seems to me that the functions If someone needs to traverse a structure right-to-left, they reverse the structure. I'd be curious to see the use cases for right-to-left monadic traversal in the wild. I couldn't find examples of it with search on hoogle and github. If it is done manually with recursion, it would be hard to find, though. |
I updated the implementation to address the feedback three weeks ago. The Merge Request introduces these two functions: mapAccumM
:: (Monad m, Traversable t)
=> (s -> a -> m (s, b)) -> s -> t a -> m (s, t b)
forAccumM
:: (Monad m, Traversable t)
=> s -> t a -> (s -> a -> m (s, b)) -> m (s, t b) Without feedback I do not have work to do for this proposal. Should this be put to vote? |
With regards to prior art and impact, Hackage Search reveals that @lykahb sorry for delay. I posted this proposal to Reddit, hopefully we can source a bit more feedback before putting this to vote. |
Please don't forget about https://discourse.haskell.org/. It's a great fit for such decisions. |
A minor suggestion: It took me a moment to think about why the constraint needed to be |
Feel free to post a link there, but please encourage to discuss the proposal here, not on Discourse. |
@lykahb happy to trigger a vote? |
@Bodigrim I updated the proposal to include both |
As I commented on the MR, the |
I agree that Dear CLC members, the proposal is to expand
and
The full implementation is available at https://gitlab.haskell.org/ghc/ghc/-/merge_requests/8384/diffs Please vote on these two additions separately. If you disagree with the proposal, please indicate whether some changes may sway your opinion (e. g., move to @tomjaguarpaw @chessai @mixphix @emilypi @cgibbard +1 for |
+1 for both. I think given the rest of the contents of the module, lacking a for-variant would seem out of place. |
I agree that it's tangential, but your claim that it's not used is unjustified. |
+1 for both! |
Updated the MR with |
+1 for both The prior art for |
Even more tangential ...
How? |
@tomjaguarpaw |
Ah, interesting. Of course, the point of the |
+1 for both |
...which gives as 5 votes in favor of |
Approved by Core Libraries Committee in haskell/core-libraries-committee#65 (comment)
Approved by Core Libraries Committee in haskell/core-libraries-committee#65 (comment)
I'm trying to summarise the state of this proposal as part of my volunteering effort to track the progress of all
Please, let me know if you find any mistakes 🙂 |
I suggest adding new utility functions to
Data.Traversable
that allow monadic effects when traversing the data with accumulator:Cabal has an implementation of it at Distribution.Utils.MapAccum:
We can reuse this implementation.
Motivation
My use case is processing a list of entries with the cache of auxiliary data that needs expensive IO operations to compute. Cabal also uses
mapAccumM
for caching and discards the accumulator.Alternative
It gets easier to use larger functions when a function is the last argument. A few times Cabal flips the arguments of
mapAccumM
or defines the processing function outside of themapAccumM
call. So, this may be more ergonomic:Impact
A search of "mapAccumM" on github yields nearly 200 results. After this change, a name clash would break many of those modules. However, this also shows that there is a need for the function.
The text was updated successfully, but these errors were encountered: