elmcraft / core-extra / Maybe.Extra

Convenience functions for Maybe.

Basics

Work with 1 Maybe

isJust : Maybe a -> Basics.Bool

isJust (Just 42)
--> True

isJust (Just [])
--> True

isJust Nothing
--> False

isNothing : Maybe a -> Basics.Bool

isNothing (Just 42)
--> False

isNothing (Just [])
--> False

isNothing Nothing
--> True

join : Maybe (Maybe a) -> Maybe a

Flattens nested Maybes

join (Just (Just 1))
--> Just 1

join (Just Nothing)
--> Nothing

join Nothing
--> Nothing

filter : (a -> Basics.Bool) -> Maybe a -> Maybe a

Keep the Maybe only if the predicate function passes

filter (\v -> v == 1) (Just 1)
--> Just 1

filter (\v -> v == 2) (Just 1)
--> Nothing

filter (\v -> v == 1) Nothing
--> Nothing

Get a value out of a Maybe

withDefaultLazy : (() -> a) -> Maybe a -> a

Lazy version of Maybe.withDefault.

It will only calculate the default if needed.

Examples:

withDefaultLazy (\() -> 2 + 2) Nothing
--> 4

withDefaultLazy (\() -> Debug.todo "Expensive calculation") (Just 4)
--> 4

unwrap : b -> (a -> b) -> Maybe a -> b

Like using a case. Give a function that says what to do if the input is Just, and a value to use if the input is Nothing.

These are all equivalent:

unwrap default f maybeX

maybeX
    |> Maybe.map f
    |> Maybe.withDefault default

case maybeX of
    Just x ->
        f x

    Nothing ->
        default

Except that unlike a case, the default value for unwrap is always computed. If your default value is expensive to compute, use the lazy unpack instead.

Examples:

unwrap 0 String.length Nothing
--> 0

unwrap 0 String.length (Just "abc")
--> 3

unpack : (() -> b) -> (a -> b) -> Maybe a -> b

Like unwrap, but the default value is lazy, and will only be computed if the Maybe is Nothing.

unpack (\() -> 0) String.length Nothing
--> 0

unpack (\() -> 0) String.length (Just "abc")
--> 3

unpack (\() -> default) f maybeX is equivalent to

case maybeX of
    Just x ->
        f x

    Nothing ->
        default

OR based logic

Take the first value that's present

or : Maybe a -> Maybe a -> Maybe a

Returns the first value that is present, like the boolean ||.

Both values will be computed. There is no short-circuiting. If your second argument is expensive to calculate and you need short circuiting, use orLazy instead.

or (Just 4) (Just 5)
--> Just 4

or (Just 4) Nothing
--> Just 4

or Nothing (Just 5)
--> Just 5

or Nothing Nothing
--> Nothing

Advanced functional programmers will recognize this as the implementation of mplus for Maybes from the MonadPlus type class.

orElse : Maybe a -> Maybe a -> Maybe a

Piping-friendly version of or.

Just 5
    |> orElse (Just 4)
--> Just 5

orElse (Just 4) (Just 5)
--> Just 5

List.head []
    |> orElse (List.head [ 4 ])
--> Just 4

orList : List (Maybe a) -> Maybe a

Returns the first value that is present.

All values will be computed. If your arguments are expensive to calculate, use orListLazy instead.

orList
    [ Nothing
    , Just 1
    , Just 2
    ]
--> Just 1

orList
    [ List.head []
    , String.toInt ""
    ]
--> Nothing

orList []
--> Nothing

orLazy : Maybe a -> (() -> Maybe a) -> Maybe a

Lazy version of or.

The second argument will only be evaluated if the first argument is Nothing.

orLazy (Just 4) (\() -> Debug.todo "Expensive calculation")
--> Just 4

orElseLazy : (() -> Maybe a) -> Maybe a -> Maybe a

Lazy version of orElse. Piping-friendly version of orLazy.

The first argument will only be evaluated if the second argument is Nothing.

Just 4
    |> orElseLazy (\() -> Debug.todo "Expensive calculation")
--> Just 4

orListLazy : List (() -> Maybe a) -> Maybe a

Lazy version of orList

Stops calculating new values after the first match

orListLazy
    [ \() -> Nothing
    , \() -> Just 1
    , \() -> Debug.todo "Expensive calculation"
    ]
--> Just 1

oneOf : List (a -> Maybe b) -> a -> Maybe b

Try a list of functions against a value. Return the value of the first call that succeeds (returns Just).

type UserInput
    = FloatInput Float
    | IntInput Int
    | UnknownInput

"5.6"
    |> oneOf
        [ String.toInt >> Maybe.map IntInput
        , String.toFloat >> Maybe.map FloatInput
        ]
    |> Maybe.withDefault UnknownInput
--> FloatInput 5.6

Lists of Maybes

values : List (Maybe a) -> List a

Take all the values that are present, throwing away any Nothings.

Equivalent to List.filterMap identity.

values [ Just 1, Nothing, Just 2 ]
--> [ 1, 2 ]

Combining

combine : List (Maybe a) -> Maybe (List a)

If every Maybe in the list is present, return all of the values unwrapped. If there are any Nothings, the whole function fails and returns Nothing.

combine []
--> Just []

combine [ Just 1, Just 2, Just 3 ]
--> Just [ 1, 2, 3 ]

combine [ Just 1, Nothing, Just 3 ]
--> Nothing

combineMap : (a -> Maybe b) -> List a -> Maybe (List b)

Like combine, but map a function over each element of the list first.

If every function call succeeds (returns Just), combineMap will return a list. If any function call fails (returns Nothing), combineMap will return Nothing.

combine is equivalent to combineMap identity.

combineMap (\x -> Just (x * 10)) [ 1, 2, 3, 4, 5 ]
--> Just [ 10, 20, 30, 40, 50 ]

combineMap List.head [ [1], [2, 3], [] ]
--> Nothing

combineArray : Array (Maybe a) -> Maybe (Array a)

Like combine, but works on Array instead of List.

combineMapArray : (a -> Maybe b) -> Array a -> Maybe (Array b)

Like combineMap, but works on Array instead of List.

combineFirst : ( Maybe a, c ) -> Maybe ( a, c )

Pull a maybe out of the first element of a tuple and combine it into a maybe holding the tuple's values.

combineSecond : ( c, Maybe a ) -> Maybe ( c, a )

Pull a result out of the second element of a tuple and combine it into a result holding the tuple's values. Also known as sequence on tuples.

combineBoth : ( Maybe a, Maybe b ) -> Maybe ( a, b )

Combine all maybes in a tuple into a single maybe holding the tuple's values.

combineMapFirst : (a -> Maybe b) -> ( a, c ) -> Maybe ( b, c )

Map a function producing maybes on the first element of a tuple and then pull it out using combineFirst.

combineMapFirst f ( x, y )
    == combineFirst (Tuple.mapFirst f ( x, y ))
    == Maybe.map (\newX -> ( newX, y )) (f x)

combineMapSecond : (a -> Maybe b) -> ( c, a ) -> Maybe ( c, b )

Map a function producing maybes on the second element of a tuple and then pull it out using combineSecond.

combineMapSecond f ( x, y )
    == combineSecond (Tuple.mapSecond f ( x, y ))
    == Maybe.map (Tuple.pair x) (f y)

combineMapBoth : (a -> Maybe c) -> (b -> Maybe d) -> ( a, b ) -> Maybe ( c, d )

Map a function producing maybes on the both elements of a tuple and then pull them out using combineBoth.

combineMapBoth f g ( x, y )
    == combineBoth (Tuple.mapBoth f g ( x, y ))
    == Maybe.map2 Tuple.pair (f x) (g y)

Transforming to collections

toList : Maybe a -> List a

A Maybe is a lot like a list that can only be length 0 or 1.

Returns a singleton list if the value is present, and an empty list it's missing.

toList Nothing
--> []

toList (Just 1)
--> [ 1 ]

toArray : Maybe a -> Array a

Like toList, but returns a singleton or empty Array.

import Array

toArray Nothing
--> Array.fromList []

toArray (Just 1)
--> Array.fromList [ 1 ]

cons : Maybe a -> List a -> List a

Add an item to a list only if it's a Just.

cons (Just 1) [ 2, 3 ]
--> [ 1, 2, 3 ]

cons Nothing [2, 3 ]
--> [ 2, 3 ]

andThenN

These functions are just like andThen, except they take multiple arguments.

All arguments must be Just and the function must return a Just for the maybe to be Just.

If you need a version of andThenN that takes more than 4 arguments, you can chain together andMap calls in a pipeline.

andThen2 : (a -> b -> Maybe value) -> Maybe a -> Maybe b -> Maybe value

import Array exposing (Array)

array : Array Int
array = Array.fromList [1,2,3]

andThen2 Array.get (Just 1) (Just array)
--> Just 2

andThen2 Array.get Nothing (Just array)
--> Nothing

andThen2 Array.get (Just 1) Nothing
--> Nothing

andThen2 Array.get (Just 4) (Just array)
--> Nothing

andThen3 : (a -> b -> c -> Maybe value) -> Maybe a -> Maybe b -> Maybe c -> Maybe value

andThen for 3 maybes.

andThen4 : (a -> b -> c -> d -> Maybe value) -> Maybe a -> Maybe b -> Maybe c -> Maybe d -> Maybe value

andThen for 4 maybes.

Applicative Functions

andMap : Maybe a -> Maybe (a -> b) -> Maybe b

If both a function and a value are present, apply the function to the value. If either argument is Nothing, return Nothing.

Just ((+) 2)
    |> andMap (Just 3)
--> Just 5

Nothing
    |> andMap (Just 3)
--> Nothing

Just ((+) 2)
    |> andMap Nothing
--> Nothing

This can be used to do Maybe.mapN or andThenN for any number of arguments.

-- map4
Just (\a b c d -> a + b + c + d )
    |> andMap (Just 1)
    |> andMap (Just 2)
    |> andMap (Just 4)
    |> andMap (Just 8)
--> Just 15

-- andThen4
Just (\a b c d -> Just (a + b + c + d ))
    |> andMap (Just 1)
    |> andMap (Just 2)
    |> andMap (Just 4)
    |> andMap (Just 8)
    |> join
--> Just 15

Advanced functional programmers will recognize this as the implementation of <*> for Maybes from the Applicative typeclass.

next : Maybe a -> Maybe b -> Maybe b

Take two Maybe values. If the first one equals Nothing, return Nothing. Otherwise return the second value.

next (Just 1) (Just 2)
--> Just 2

next Nothing (Just 2)
--> Nothing

next (Just 1) Nothing
--> Nothing

Advanced functional programmers will recognize this as the implementation of *> for Maybes from the Applicative typeclass.

prev : Maybe a -> Maybe b -> Maybe a

Take two Maybe values. If the second one equals Nothing, return Nothing. Otherwise return the first value.

prev (Just 1) (Just 2)
--> Just 1

prev Nothing (Just 2)
--> Nothing

prev (Just 1) Nothing
--> Nothing

Advanced functional programmers will recognize this as the implementation of <* for Maybes from the Applicative typeclass.