Early return from function on Err or Nothing

In Rust when matching on Result or Option types I can return early from the function call with the return keyword. Is there an equivalent way to do that in Elm when matching Result or Maybe types?

You can use Result.andThen or Maybe.andThen in function pipelines to achieve the same effect.

1 Like

Elm functions are a single expression that is evaluated to some value. They are not defined to evaluate in any particular order so the concept of ā€˜earlyā€™ isnā€™t meaningful and thus you canā€™t ā€˜return earlyā€™ from a function.

If youā€™re matching on a Maybe a you can handle the Nothing as the first case in a case..of can be similar.

eg.

case someMaybe of
    Nothing -> 
        5 -- Some value in the case of a Nothing
    Just x -> 
        x + 1 -- The longer expression if you have the value

Functions like map and andThen are helpful wrappers for these kinds of case..ofs

1 Like

Its probably worth illustrating this with an example. Lets suppose our error type is String and result type is Int, throughout some series of functions that we are going to pipeline together. In this example the pipeline is a chain of computations starting from some Int input:

firstFun : Int -> Result String Int
firstFun val = ...

secondFun : Int -> Result String Int
secondFun val = ...

thirdFun : Int -> Result String Int
thirdFun val = ...

applyFuns : Int -> Result String Int
applyFuns val = 
    firstFun val
    |> Result.andThen secondFun
    |> Result.andThen thirdFun

If the first fun should fail and produce a String error message, the second and third will not be run. The overall result will be that error message wrapped in Err

If second fails, third will not run, and the error message wrapped in Err from the second will be the result.

If third fails, its error message wrapped in Err will be the result.

If they all succeed, the result will be the overall result of passing that Int value down this pipeline, wrapped in Ok.

Sometimes the error types donā€™t align down a pipeline. In which case you might use Result.mapError to map errors onto some common error type.

3 Likes

Rustā€™s option and result types already have .map and .andThen functions defined on them, so Iā€™ll assume youā€™re familiar or have at least used them briefly.

There is a fundamental difference between a language like Elm and a language like Rust; everything in Elm is an expression. There are no statements. This means we donā€™t have control flow statements like return or break and even if ... then ... else is an expression that evaluates to a single value.

Instead, we use values to determine the flow of our programs, and thatā€™s when functions like Result.map or Maybe.andThen become useful. It means we can define a ā€œhappyā€ path, when the value is good and a bad path when the value is an error (or Nothing in the case of maybe).

Iā€™m not sure I would recommend it but you could ā€œfakeā€ an early return by simply pattern matching and handling the bad case immediately:

case result of
  Ok value ->
    value
      |> foo
      |> bar
      |> baz

  Err _ ->
    42

But not that the same could be written as:

result
  |> Result.map foo
  |> Result.map bar
  |> Result.map baz
  |> Result.withDefault 42

Or more concisely:

result
  |> Result.map (foo >> bar >> baz)
  |> Result.withDefault 42
2 Likes

Thanks to everyone for the help. ā€œandThenā€ really means ā€œifJustThenā€ or ā€œifOkThenā€. I was worried about having to handle the error in the second and third functions in a pipeline.

Another name for ā€œandThenā€ is ā€œflatMapā€ (map, then flatten).

Leaving the original post below but I did finally figure this out, I think. Recursion is the secret.

isIsogram : String -> Bool
isIsogram sentence =
  recursiveCheck Set.empty sentence

recursiveCheck : Set Char -> String -> Bool 
recursiveCheck set input =
  case String.uncons input of
    Nothing -> True 
    Just (c, sub) -> 
      if not (Char.isAlpha c) then 
         recursiveCheck set sub
      else if Set.member (Char.toUpper c) set then 
        False 
      else 
        recursiveCheck (Set.insert (Char.toUpper c) set) sub

================================
I think Iā€™m still not quite getting this. For instance, in the exercism Isogram problem, a naive solution is to take the input string, convert it to a set of chars and compare the size of the set with the length of the string. This iterates over all characters in the string twice, once for creating the set and once for the comparison. How would I iterate over the characters, inserting into the set if it does not exist in the set but returning false if it does exist in the set and only returning true if it reaches the end of the input string without triggering a false?
In Rust I would do something like this:

fn use_hashset(word: &str) -> bool {
    let mut ltrs = HashSet::new();
    word.chars()
        .all(|c| ltrs.insert(c))
}

which is possible because the HashSet insert method returns a bool and because the HashSet is declared as mutable. I suspect the problem Iā€™m having is primarily due to the inability to have a mutable Set in Elm, but Iā€™m curious what the alternative is. FWIW I did not find any one elseā€™s solution on Exercism that did not iterate over all characters twice, so maybe itā€™s just not possible.

Iā€™ve tried this:

module Isogram exposing (isIsogram)

import Set exposing (Set)

isIsogram : String -> Bool
isIsogram sentence =
  let s = Set.empty 
  in
    String.toUpper sentence |>
    String.filter Char.isAlpha |>
    String.toList |>
    List.all (\c -> 
      
      if Set.member c s then 
        Debug.log (Debug.toString s)
        False
      else 
        Debug.log (Debug.toString s)
        Set.insert c s |> (\_ -> True)
        )


and the Debug logging confirms that the Set s remains empty so all input strings return True.

FWIW I recognize the multiple iterations in ā€œcleaning upā€ the input string. One thing at a time.

Yes, thatā€™s exactly how Iā€™d do it.

isIsogram string =
    string
        |> String.toList
        |> Set.fromList
        |> Set.size
        |> (==) (String.length string)

Is there anything wrong with that approach?

Edit: Just realized thereā€™s an example in Exercism where you should ignore dashes, but thatā€™s easy enough to add in.

I donā€™t think there is anything wrong with that solution, but it is not the most efficient.
Building the Set requires going over all characters twice ā€“ once to add each char to a list, and once to insert each char from the list into the set. For this problem that doesnā€™t matter because all the input strings are relatively short, but for very long strings it would be nice to have an On rather than On^2 approach. And if youā€™re adding in a step to filter out non-alpha chars and then another step to make all chars the same case, you can even end up with On^4 as you iterate over the whole list in each step.

I eventually figured out how to avoid that. You can see it in my edited post above. FWIW my Big O notation may be off if the Set.member check is also On time.

I see! :+1:

(Sorry, I didnā€™t realize your post above was edited. The new stuff above the ====== line.)

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.