Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# idiomatic Try Until

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.

like image 683
RomnieEE Avatar asked Aug 04 '16 23:08

RomnieEE


1 Answers

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.

like image 150
CaringDev Avatar answered Oct 06 '22 07:10

CaringDev