I’m doing exercism, and in one problem they define the type of a function to be
Result<uint64,string>
I have no idea what that type is: could someone explain it, and or point me to somewhere in the docs that explains it.
I looked at other peoples solutions and they are returning something like
Hi Jonathan, and welcome! Result in F# is a way to communicate errors or failure via the return type, as an alternative to just throwing an Exception. Let’s look at the type definition of Result from FSharp.Core:
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
If that syntax isn’t familiar to you, it’s a “discriminated union,” and this article describes the concept pretty well. This type definition means that a value of type Result<uint64, string> could be either an Ok of some uint64, or an Error of some string. So:
let a : Result<uint64, string> = Ok 5UL // valid
let b : Result<uint64, string> = Error "overflow" // also valid
If you have a Result, and you want to do something with it, the simplest thing to do is pattern match it:
match myResult with
| Ok i -> // do something here with i, which is a uint64
| Error e -> // do something here with e, which is a string
(Though the Result module has a few other helpful functions that can sometimes make working with Results more succinct, like Result.map and Result.mapError).
There’s some debate in the F# community over when it’s more appropriate to use Result to handle errors, and when it’s more appropriate to throw Exceptions for errors instead. There’s a popular concept among F#ers called “Railway Oriented Programming” that heavily features Result.
The Result<'t, 'u>type is a discriminated union that is defined in the FSharp.Core library. It is defined as:
type Result<'t, 'u> =
| Ok of 't
| Error of 'u
The idea of returning this type is that a caller needs to explicitly handle both the case of a successful call and the case of an error. In your case, it is Result<uint64, string>, so if the function is successful it will return Ok number where number is a uint64. If the function fails, it will return Error str where str is a string, propbably explaining why the function failed. Say, for example, you are trying to write a function to perform safe division. You want to return a number if the division works, but if the denominator is zero, you want to return an error message. You could implement that like so:
let saveDivision (numerator: uint64) (denominator: uint64): Result<uint64, string> =
if denominator = 0UL then
Error "Division by zero"
else
Ok (numerator / denominator)
Then, a caller of that function can explicitly handle both cases:
let numerator = 10UL
let denominator = 0UL
let result = saveDivision numerator denominator
match result with
| Ok value -> printfn $"{numerator} / {denominator} = {value}"
| Error message -> printfn $"Error dividing {numerator} by {denominator}: {message}"