5 ms·
In the article he posts a snippet of F# code: type Customer = { Id : string; Credit : decimal option } let average (customers : Customer list) =
by ragnese 7y ago
In the article he posts a snippet of F# code:
type Customer = { Id : string; Credit : decimal option }
let average (customers : Customer list) =
match customers with
| [] -> Error "list was empty"
| _ ->
customers
|> List.averageBy (fun c -> c.Credit.Value)
|> Ok
And argues that the F# programmer must conclude that all functions may throw because this function accesses the Option.Value without checking that it isn't None first and there's nothing in the type signature of the function to indicate that it may throw.
Is that really how F# works? Similar code wouldn't compile in Rust. You have to handle both cases for Option<T> every time you want the value (or, yes, you can get the value by force, but if you do that, it's definitely NOT an accident and you're basically acknowledging that you want the whole program to crash if the value is None).
Anyway, I'm still in the Either/Result/Try camp. I even used Result/Try types in Java, Kotlin, and Swift (before they officially showed up in Swift and Kotlin).
I think the extra boiler plate in the middle of a function chain is absolutely worth it. I hate the idea that I need to actually read the source code of every function I call from library X just to see if it will throw an exception on me.
Swift has a pretty good compromise, IMO. A function's signature must indicate that it throws, but it doesn't have any type indicated. At least I know when I need to investigate a function further!
- UK-Al05 7y agoThats not how f# works normally. Normally you chain option operations with either binds, or you do a match(Which is exhaustive). Directly accessing the value is normally frowned upon. This guy either needed to make the option type go away, or filter out the None and pass that into average. List.choose could be used for this let average (customers : Customer list) = match customers with | [] -> Error "list was empty" | _ -> customers |> List.choose(fun c -> c.Credit) # Would remove the option type. Any nones would be filtered out the list |> List.average |> Ok This is the best I could think off without a computer
- vikestep 7y agoIn support of the article, your code would still throw an exception if all the customers in the list have missing credit. https://github.com/dotnet/fsharp/blob/master/src/fsharp/FSharp.Core/list.fs#L627 https://github.com/dotnet/fsharp/blob/master/src/fsharp/FSha... You could instead write it as the following to handle that. let average (customers : Customer list) = customers |> List.choose (fun c -> c.Credit) |> function | [] -> Error "no customers with credit" | xs -> Ok (List.average xs)
- UK-Al05 7y agoIndeed. I was remembering the behaviour of average off the top of my head.