Trident D'Gao
Trident D'Gao

Reputation: 19690

Standard Haskell function :: (a -> Maybe b) -> [a] -> Maybe b

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

?

Upvotes: 13

Views: 1975

Answers (4)

Aaron Roth
Aaron Roth

Reputation: 456

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!

Upvotes: 8

Gabriella Gonzalez
Gabriella Gonzalez

Reputation: 35089

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.

Upvotes: 16

J. Abrahamson
J. Abrahamson

Reputation: 74334

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.

Upvotes: 12

Josh Lee
Josh Lee

Reputation: 177530

The listToMaybe function in Data.Maybe looks pretty good:

tryPick f = listToMaybe . mapMaybe f

Upvotes: 14

Related Questions