elmcraft / core-extra / Result.Extra

Convenience functions for working with Result.

Common Helpers

isOk : Result e a -> Basics.Bool

Check whether the result is Ok without unwrapping it.

isErr : Result e a -> Basics.Bool

Check whether the result is Err without unwrapping it.

extract : (e -> a) -> Result e a -> a

Turn a Result e a to an a, by applying the conversion function specified to the e.

unwrap : b -> (a -> b) -> Result e a -> b

Convert a Result e a to a b by applying a function if the Result is Ok or using the provided default value if it is an Err.

unpack : (e -> b) -> (a -> b) -> Result e a -> b

Convert a Result e a to a b by applying either the first function if the Result is an Err or the second function if the Result is Ok. Both of these functions must return the same type.

error : Result e a -> Maybe e

Convert to a Maybe containing the error, if there is one.

parseInt : String -> Result ParseError Int

maybeParseError : String -> Maybe ParseError
maybeParseError string =
    error (parseInt string)

mapBoth : (e -> f) -> (a -> b) -> Result e a -> Result f b

Apply the first argument function to an Err and the second argument function to an Ok of a Result.

merge : Result a a -> a

Eliminate Result when error and success have been mapped to the same type, such as a message type.

merge (Ok 4) == 4

merge (Err -1) == -1

More pragmatically:

type Msg
    = UserTypedInt Int
    | UserInputError String

msgFromInput : String -> Msg
msgFromInput =
    String.toInt
        >> Result.mapError UserInputError
        >> Result.map UserTypedInt
        >> Result.Extra.merge

join : Result x (Result x a) -> Result x a

Join contained results with the same error into one result.

Usefull if you have a "result in a result":

join <| Ok (Ok 4) == Ok 4

join <| Ok (Err "message") == Err "message"

partition : List (Result e a) -> ( List a, List e )

Partition a list of Results into two lists of values (successes and failures), much as List.partition takes a predicate and splits a list based on whether the predicate indicates success or failure.

partition ( Ok 4, Err "no", Err "hi" ) == ( [ 4 ], [ "no", "hi" ] )

partition ( Err 7.1, Ok 'k', Err 9.0, Ok 'p' ) == ( [ 'k', 'p' ], [ 7.1, 9.0 ] )

filter : e -> (a -> Basics.Bool) -> Result e a -> Result e a

Take a Result and a predicate function and return a Result with the original value when a predicate matches.

filter "is not 1" (\v -> v == 1) (Ok 1) == Ok 1

filter "is not 2" (\v -> v == 2) (Ok 1) == Err "is not 2"

Combining

combine : List (Result x a) -> Result x (List a)

Combine a list of results into a single result (holding a list). Also known as sequence on lists.

combineMap : (a -> Result x b) -> List a -> Result x (List b)

Map a function producing results on a list and combine those into a single result (holding a list). Also known as traverse on lists.

combineMap f xs == combine (List.map f xs)

combineArray : Array (Result x a) -> Result x (Array a)

Like combine, but works on Array instead of List.

combineMapArray : (a -> Result x b) -> Array a -> Result x (Array b)

Like combineMap, but works on Array instead of List.

combineFirst : ( Result x a, c ) -> Result x ( a, c )

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

combineSecond : ( c, Result x a ) -> Result x ( 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 : ( Result x a, Result x b ) -> Result x ( a, b )

Combine all results in a tuple into a single result holding the tuple's values. Also know as bisequence on tuples.

combineMapFirst : (a -> Result x b) -> ( a, c ) -> Result x ( b, c )

Map a function producing results on the first element of a tuple and then pull it out using combineFirst. Also know as sequence on tuples.

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

combineMapSecond : (a -> Result x b) -> ( c, a ) -> Result x ( c, b )

Map a function producing results on the second element of a tuple and then pull it out using combineSecond. Also know as traverse on tuples.

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

combineMapBoth : (a -> Result x c) -> (b -> Result x d) -> ( a, b ) -> Result x ( c, d )

Map a function producing results on the both elements of a tuple and then pull them out using combineBoth. Also know as bitraverse on tuples.

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

Applying

andMap : Result e a -> Result e (a -> b) -> Result e b

Apply the function that is inside Result to a value that is inside Result. Return the result inside Result. If one of the Result arguments is Err e, return Err e. Also known as apply.

Err "Oh" |> andMap (Err "No!") == Err "Oh"

Err "Oh" |> andMap (Ok 2) == Err "Oh"

Ok ((+) 1) |> andMap (Err "No!") == Err "No!"

Ok ((+) 1) |> andMap (Ok 2) == Ok 3

Alternatives

or : Result e a -> Result e a -> Result e a

Like the Boolean || this will return the first value that is positive (Ok). However, unlike with ||, both values will be computed anyway (there is no short-circuiting).

or (Ok 4) (Ok 5) == Ok 4

or (Err "Oh!") (Ok 5) == Ok 5

or (Ok 4) (Err "No!") == Ok 4

or (Err "Oh!") (Err "No!") == Err "No!"

As the last example line shows, the second error is returned if both results are erroneous.

orLazy : Result e a -> (() -> Result e a) -> Result e a

Non-strict version of or. The second argument will only be evaluated if the first argument is an Err.

orElseLazy : (() -> Result e a) -> Result e a -> Result e a

Piping-friendly version of orLazy. The first argument will only be evaluated if the second argument is an Err. Example use:

String.toInt "Hello"
    |> orElseLazy (\() -> String.toInt "42")

orElse : Result e a -> Result e a -> Result e a

Strict version of orElseLazy (and at the same time, piping-friendly version of or).

orElse (Ok 4) (Ok 5) == Ok 5 -- crucial difference from `or`

orElse (Err "Oh!") (Ok 5) == Ok 5

orElse (Ok 4) (Err "No!") == Ok 4

orElse (Err "Oh!") (Err "No!") == Err "Oh!" -- also different from `or`

Also:

String.toInt "Hello"
    |> orElse (String.toInt "42")

Conversions

toTask : Result x a -> Task x a

Convert a Result to a Task that will fail or succeed immediately.

toTask (Ok 4) == Task.succeed 4

toTask (Err "msg") == Task.fail "msg"

This can be helpful when the value of a succeeding Task needs to be decoded, but a failure to decode should result in a failing Task, not a succeeding Task containing a Result.Err:

andThenDecode : (a -> Result x b) -> Task x a -> Task x b andThenDecode decode = Task.andThen (decode >> Result.Extra.toTask)