Alex Gordon
Alex Gordon

Reputation: 301

Why would the signature be ... -> Result<char*string>?

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> :

vscode screenshot

Should the return type be just Result<'a>?

Upvotes: 2

Views: 89

Answers (3)

Aaron M. Eshbach
Aaron M. Eshbach

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

@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

kagetoki
kagetoki

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

Related Questions