Reputation: 301
I'm following Scott's talk, and one of the examples he brings is:
type Result<'a> =
| Success of 'a
| Failure of string
let pchar (charToMatch, input) =
if System.String.IsNullOrEmpty (input) then
Failure "no input!"
else
let first = input.[0]
if first = charToMatch then
let remaining = input.[1..]
Success (charToMatch, remaining)
else
let msg = sprintf "exepecting '%c' got '%c'" charToMatch first
Failure msg
Visual Studio Code is showing that the signature of the pchar
function is:
char*string->Result<char*string>
:
Should the return type be just Result<'a>
?
Upvotes: 2
Views: 89
Reputation: 6510
The idea here is that you can use a Result
type to wrap the outcome of any operation such that you can model the control flow with your business logic. This is especially useful in scenarios where there is overlap between "business errors" and "exceptions". In your example, a string is being parsed one character at a time, and the function is returning the parsed character along with the rest of the string (as an F# Tuple).
Result
is a generic type, so it can represent the the result of any operation. The value of the 'a
type-parameter for the Result
type will be identical to the return type of the function if it did not return a Result
. Consider a simple example like the following:
let f () = ('t', "est")
The compiler tells us this has the type signature unit -> char * string
. That's because the function always takes a unit
(empty/void) parameter and returns a tuple of a single character and a string. If we change that function slightly to return a Result
, like this:
let f () = ('t', "est") |> Success
Now the compiler tells us the signature is unit -> Result<char * string>
. The same as the original return type of the function, wrapped in the Result
type. Your example is just the same, except that the character and string tuple returned are based on the input parameters, and the function can return a failure if the input parameter is null or an empty string.
You could easily work with your Result
types if you define a bind
function that "unwraps" the result and passes the value inside it to another function, f
:
let bind f = function
| Success s -> f s
| Failure f -> Failure f
Using bind
, you could then easily pass the result of your pchar
call to another function that uses the value, without that function having to explicitly take a Result
type:
let printChar (c, str) =
printfn "Matched Char %A, Remaining String: '%s'" c str
pchar ('a', "aaab") |> bind (printChar >> Success)
I often use this pattern, but I add a second generic parameter to the Result
type that represents the events
or effects
of the function. This increases the utility of the Result
type by allowing us to have specific Failure
events that we can handle in code (rather than checking for specific strings), or by including domain events (such as warnings) or side-effects along with our successful results.
I use a type called OperationResult
for this, to avoid confusion with the built-in F# Result
type. Since I sometimes include events/effects successful results as well as failures, I define a type for SuccessfulResult
that is either just the return value, or the return value along with a list of events. All together, that gives the following types to define an OperationResult
:
/// Represents the successful result of an Operation that also yields events
/// such as warnings, informational messages, or other domain events
[<Struct>]
type SuccessfulResultWithEvents<'result,'event> =
{
Value: 'result
Events: 'event list
}
/// Represents the successful result of an Operation,
/// which can be either a value or a value with a list of events
[<Struct>]
type SuccessfulResult<'result,'event> =
| Value of ResultValue: 'result
| WithEvents of ResultWithEvents: SuccessfulResultWithEvents<'result,'event>
member this.Result =
match this with
| Value value -> value
| WithEvents withEvents -> withEvents.Value
member this.Events =
match this with
| Value _ -> []
| WithEvents withEvents -> withEvents.Events
/// Represents the result of a completed operation,
/// which can be either a Success or a Failure
[<Struct>]
type OperationResult<'result,'event> =
| Success of Result: SuccessfulResult<'result,'event>
| Failure of ErrorList: 'event list
I have all this packaged up in a library on GitHub (and NuGet) along with a Computation Expression called operation
if you want to use it. The GitHub page has some examples as well.
Upvotes: 2
Reputation: 11577
@kagetoki is correct that F# infers the type from the usage.
Also, this look like a parser combinator library. The purpose of pchar
is to attempt to extract a character from the input. If successful return the character and the rest of the string, a natural way to represent that is char*string
. This is wrapped in a Result
to support failures therefore the final return type should be: Result<char*string>
.
Upvotes: 1
Reputation: 4777
Result<'a>
is a generic type which has 'a
as a type parameter. When you write Success (charToMatch, remaining)
compiler infers this generic type parameter as a tuple of char
and string
: Result<char*string>
. You can write Success ()
and you'll get a Result<unit>
type.
Also in F# when you list arguments for your function in parentheses with commas like you did (charToMatch, input)
, it means your function's expecting a tuple, which means you can't use currying. For currying to be available you should define function like that:
let pchar charToMatch input = ...
Upvotes: 5