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.
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..of
s
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.
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
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!
(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.