4 ms·
I'm smiling a bit because I know exactly what you mean and generally agree, with an exception. A common idiom in Elixir is to return {:ok, result} or {:err, :re
by tonyarkles 3y ago
I'm smiling a bit because I know exactly what you mean and generally agree, with an exception. A common idiom in Elixir is to return {:ok, result} or {:err, :reason} from calls that can fail. Leaning on that idiom, good function names, and good errors goes a long way:
params[:id]
|> find_user
|> verify_user_has_access_to(params[:post_id])
|> etc
When I define the functions I'll use pattern matching on the first argument to each function so that you can either pass in a "user" or {:ok, user} or {:err, _}. If it matches the error pattern it'll just return the error unmodified. The errors have enough fidelity to make it clear where the pipeline failed. I'm not sure this is a super common pattern though but it worked pretty well for me.
- sdeframond 3y agoI call it "railway oriented programming", after the presentation by Scott Wlaschin. https://fsharpforfunandprofit.com/rop/ https://fsharpforfunandprofit.com/rop/
- nephanth 3y agoI am not sure about Elexir, but in Ocaml, someone using this style would gravitate towards Result.map and Result.bind (Defined like this: given (Ok a) or (Err b), - (Result.map f) returns (Ok (f(a))) or (Err b) - (Result.bind f) returns f(a) or (Err b) ) One could then write params.id |> find_user |> Result.bind (verify_user_has_access_to params.post_id) |> etc Reasons for that are 2-fold 1. It de-clutters your functions (you do not need that match statement anymore) 2. It becomes evident - which functions will simply pass down errors (bind or map) vs. which ones may handle them - which functions may raise new errors (bind can, map can't)