Skip to content
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 function for (Maybe a, b) -> Maybe (a, b) #100

Closed
qwbarch opened this issue Jan 23, 2023 · 5 comments
Closed

Add function for (Maybe a, b) -> Maybe (a, b) #100

qwbarch opened this issue Jan 23, 2023 · 5 comments

Comments

@qwbarch
Copy link

qwbarch commented Jan 23, 2023

It'd be cool if you'd accept these functions:

maybeFst :: (Maybe a, b) -> Maybe (a, b)
maybeFst = uncurry . flip $ fmap . flip (,)

maybeSnd :: (a, Maybe b) -> Maybe (a, b)
maybeSnd = uncurry $ fmap . (,)

Not sure what to name these, but if this feature request is welcome, I'd be happy to create a pull request!

@ndmitchell
Copy link
Owner

Thanks for the suggestion. How often do you think these patterns occur in practice? Would be good to get a sense of how useful they would be.

@qwbarch
Copy link
Author

qwbarch commented Jan 23, 2023

Thanks for the reply! For me it's when I'm traversing a list and zip it up to return a map:

  • traverse a list that returns [Maybe b]
  • zip the previous list with [a]
  • convert [(a, Maybe B)] to [Maybe (a, b)]
  • keep only the available values: [(a, b)]

For example:

newtype UserId = UserId Int deriving (Eq, Ord)
data User

getUserById :: UserId -> m (Maybe User)
getUserById = undefined

getUsersById :: Monad m => [UserId] -> m (Map UserId User)
getUsersById userIds =
  fromList . mapMaybe maybeSnd . zip userIds <$> traverse getUserById userIds

@ndmitchell
Copy link
Owner

Would be interesting to get a sense of how often this occurs. One way would be to add an hlint rule and run it over a reasonable volume of Haskell code to show how often this shows up (or maybe just grep for it). Another would be to ask on Twitter/Reddit or similar if people have this issue.

@phadej
Copy link

phadej commented Mar 11, 2023

strong :: Functor f => (a, f b) -> f (a, b)
strong (a, fb) = fmap (a,) fb

is common enough pattern it has a name (strong functors), and all haskell Functors are strong in respect to products. But you would write it as uncurried in Haskell

strong :: Functor f => a -> f b -> f (a, b)
strong a fb = fmap (a,) fb

and then

getUsersById :: Monad m => [UserId] -> m (Map UserId User)
getUsersById userIds =
  fromList . mapMaybe maybeSnd . zip userIds <$> traverse getUserById userIds

I'd write that as

getUsersById :: Monad m => [UserId] -> m (Map UserId User)
getUsersById usedIds = fromList . catMaybes <$> forM userIds $ \usedId ->
    user <- getUsersById
    return (strong userId user)

but here it's easy to inline fmap (userId,) user as well.

The fmap (something,) is not uncommon on Hackage: https://hackage-search.serokell.io/?q=fmap+%5C%28%5Ba-zA-Z%5D%2B%2C%5C%29, but I'm not sure it actually would benefit from a name


\xs -> catMaybes <$> traverse f xs

is generalized to witherM from witherable. (I'd appreaciate a good name for flipped variant, like we have mapM and forM or traverse and for) (FWIW: similar request #87 for foldMap)

@qwbarch
Copy link
Author

qwbarch commented Jun 30, 2023

@phadej Quite late to seeing this. Thanks a lot for the detailed insight!

@qwbarch qwbarch closed this as not planned Won't fix, can't repro, duplicate, stale Jun 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants