F # idiomatic Try until

I have a function that takes input and either succeeds, or returns part of the input, or returns None. I will give an example of a list of phone numbers that are dialed until someone answers one of the numbers, and then the remaining numbers should be skipped. And at the end there is a logging of a successful number or error message.

My first decision, which I find too complicated, and on the first attempt, uses the sowing state of failure, 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 pulled it over with the best 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 sound like a good approach? After reading about monadic error handling, I wonder if I should do something like that.

+6
source share
1 answer

Idiom is hard to measure. I think your approach is close to the most idiomatic. And you have a Thomas subscription ... I would add a function shortcut to logCall , logCall do , pipe phoneNumbers in, eta-reduce 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 not idiomatic in .NET / F # standard-lib) I also renamed tryCallNumber only to callNumber , since the return types of IMO option types are quite obvious, and my code tends to be try - heavy and not dependent on exceptions. Similar to async -by-default libraries, where the suffix ...Async discarded.

+3
source

All Articles