13

There is a standard tryPick function if F# that returns the first (from left-to-right if any at all) successful application of a function on an element of a list. I am hopping there is a standard function like that in Haskell. I tried Hoogle and didn't find anything.

I am new to Haskell and I am not sure what the right way of doing it is. Would you do it like this:

tryPick:: (a -> Maybe b) -> [a] -> Maybe b tryPick try xs = case Maybe.mapMaybe try xs of [] -> Nothing (x:_) -> Just x 

?

1
  • One of the functions in Data.Maybe can already handle the cases you’ve written. Commented Oct 28, 2013 at 19:51

4 Answers 4

16

You want:

tryPick :: (a -> Maybe b) -> [a] -> Maybe b tryPick f as = msum (map f as) 

I'll explain how this works.

map f as produces a list of possible Maybe actions to try:

map f as :: [Maybe b] 

msum tries them sequentially until one succeeds (returning the value as a Just) or they all fail (returning a Nothing). For example:

> msum [Nothing, Just 2, Just 3, Nothing] Just 2 > msum [Nothing, Nothing] Nothing 

Note that msum's type is more general, so we can generalize the signature to:

tryPick :: (MonadPlus m) => (a -> m b) -> [a] -> m b 

This will now work for any MonadPlus. Have fun discovering what it does for other MonadPlus types.

Sign up to request clarification or add additional context in comments.

2 Comments

tryPick f = foldr (mplus . f) mzero :: (MonadPlus m, Foldable t) => (a -> m b) -> t a -> m b, best of both worlds!
"As of base 4.8.0.0, msum is just asum, specialized to MonadPlus." I'd personally always use asum rather than msum today.
14

The listToMaybe function in Data.Maybe looks pretty good:

tryPick f = listToMaybe . mapMaybe f 

5 Comments

Does this incur extra memory overhead from mapping over the whole list? I'm not sure if GHC will create a thunk for the rest of the computation or optimize it away
@jozefg It terminates given an infinite list, so intuitively it’s fine.
@jozefg I'd think so. Mentally inlining we'd have \(x:xs) -> case (f x) : tryPick f xs of { [] -> Nothing; (y:_) -> Just y }
I know it terminates I was curious about whether this will produce an extra thunk or whether it's optimized away. It's not a huge deal, more of a curiosity. Btw +1 for a clearer solution than mine
Doesn't this, on multiple successful applications, return a Just list instead of Just the first successful application?
12

It's not necessarily the simplest solution, but I feel it important to highlight the First Monoid based solution. I think it's the prettiest.

import Data.Monoid import Data.Foldable (Foldable, foldMap) tryPick :: (a -> Maybe b) -> [a] -> Maybe b tryPick f = getFirst . foldMap (First . f) -- this is just `foldMap f` -- with the "firsty" Maybe Monoid 

This is also immediately generalizable to any Foldable with precisely the same code

tryPick :: Foldable t => (a -> Maybe b) -> t a -> Maybe b 

Foldable instances provide ways to "smash" all of the elements together in order using Monoids. The First Monoid defined as

newtype First a = First { getFirst :: Maybe a } 

is a specialization of Maybe with a mappend operation that picks the "first" or "leftmost" Just.

So, putting them together, getFirst . foldMap (First . f) computes your (a -> Maybe b) function over all of the as in the [a], then smashes the results together with the rule that the "first" Just wins.

1 Comment

Alt and getAlt are more modern alternatives for First and getFirst.
8

I'm coming a bit late to the party, but here's a variation on J. Abrahamson's answer that uses Conor McBride's lovely ala' function from the newtype package:

import Control.Newtype (ala') import Data.Foldable (Foldable, foldMap) import Data.Monoid (First(..)) tryPick :: (Foldable t) => (a -> Maybe b) -> t a -> Maybe b tryPick = ala' First foldMap 

This may seem a bit cryptic, but I find the way it decouples the "collection vessel" (First) from the "collection scheme" (foldMap) and both from the "preprocessing function" (a -> Maybe b) -- all while hiding newtype wrapping and unwrapping -- rather beautiful. ala has been in my experience a fine tool for creating beautiful code, and I'd like to plug it. Thanks, Conor!

1 Comment

This is the most awesome answer I've seen for a long time. I just learned something very usefull. Thanks!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.