I have a function that takes an input and either succeeds and returns Some of the input or returns None. I'll make the example a list of phone numbers that get dialed until someone answers one of the numbers, and then the rest of the numbers should be skipped. And at the end there is a logging of a successful number or of an error message.
My first solution I think is too complicated and also uses a seeding of the failure state on the first try, which seems inelegant:
type PhoneNumber = int
let tryCallNumber phoneNumber =
if phoneNumber % 2 = 0 then Some phoneNumber
else None
let nextCall phoneNumberOption nextNumber =
match phoneNumberOption with
| Some num -> phoneNumberOption
| None -> tryCallNumber nextNumber
let logCall phoneNumberOption =
match phoneNumberOption with
| Some num -> printfn "%i" num
| None -> printfn "%s" "failed"
let phoneNumbers = [111; 222; 444; 555]
do List.fold (fun state num -> (nextCall state num)) None phoneNumbers
|> logCall
I tightened it up with a better List function, tryPick:
type PhoneNumber = int
let tryCallNumber phoneNumber =
if phoneNumber % 2 = 0 then Some phoneNumber
else None
let logCall phoneNumberOption =
match phoneNumberOption with
| Some num -> printfn "%i" num
| None -> printfn "%s" "failed"
let phoneNumbers = [111; 222; 444; 555]
do List.tryPick (fun num -> tryCallNumber num) phoneNumbers
|> logCall
Does this seem like a good approach? After reading about monadic error handling, I wonder if I should be doing something somehow more in that spirit.
Idiomaticity is difficult to measure. I think your approach is close-to-most-idiomatic. And you've got Tomas' sign-off... I would add the function
shortcut for logCall
, drop the do
, pipe phoneNumbers
in, eta-reduce the tryCallNumber
lambda:
let phoneNumbers = [111; 222; 444; 555]
let tryCallNumber phoneNumber =
if phoneNumber % 2 = 0 then Some phoneNumber
else None
let logCall = function
| Some num -> printfn "%i" num
| None -> printfn "failed"
phoneNumbers
|> List.tryPick tryCallNumber
|> logCall
Personally (unfortunately non-idiomatic in .NET / F# standard-lib), I'd also rename tryCallNumber
to just callNumber
, as IMO option
return types are obvious enough and my code tends to be try
-heavy and not reliant on exceptions. Similar to async
-by-default libraries where the ...Async
suffix is dropped.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With