为什么签名是...-> Result <char * string>?

时间:2018-07-30 02:32:16

标签: .net f#

我正在关注Scott的talk,他带来的例子之一是:

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代码显示pchar函数的签名为:

char*string->Result<char*string>

vscode screenshot

返回类型应为Result<'a>吗?

3 个答案:

答案 0 :(得分:5)

Result<'a> generic 类型,具有'a作为类型参数。当您编写Success (charToMatch, remaining)时,编译器会将此通用类型参数推断为charstring的元组:Result<char*string>。您可以编写Success (),并获得Result<unit>类型。

同样在F#中,当您像在(charToMatch, input)一样用括号将函数的参数列出在括号中时,这意味着您的函数正在使用元组,这意味着您不能使用currying。为了使currying可用,您应该定义如下函数: let pchar charToMatch input = ...

答案 1 :(得分:2)

这里的想法是,您可以使用Result类型来包装任何操作的结果,以便可以使用业务逻辑对控制流进行建模。这在“业务错误”和“异常”之间重叠的情况下特别有用。在您的示例中,一个字符串一次被解析一个字符,并且该函数正在将解析的字符与字符串的其余部分一起返回(作为F#元组)。

Result是泛型类型,因此它可以表示任何操作的结果。如果'a类型的Result类型参数的值与没有返回Result的函数的返回类型相同。考虑一个简单的示例,如下所示:

let f () = ('t', "est")

编译器告诉我们它具有类型签名unit -> char * string。这是因为该函数始终采用unit(空/无效)参数,并返回单个字符和字符串的元组。如果我们稍微更改该函数以返回Result,如下所示:

let f () = ('t', "est") |> Success

现在,编译器告诉我们签名为unit -> Result<char * string>。与函数的原始返回类型相同,包装为Result类型。您的示例是相同的,除了返回的字符和字符串元组是基于输入参数的,并且如果输入参数为null或空字符串,则函数可以返回失败。

如果定义一个Result函数来“解包”结果并将其中的值传递给另一个函数bind,则可以轻松使用f类型:

let bind f = function
| Success s -> f s
| Failure f -> Failure f

使用bind,您可以轻松地将pchar调用的结果传递给使用该值的另一​​个函数,而该函数不必显式采用Result类型:

let printChar (c, str) =
    printfn "Matched Char %A, Remaining String: '%s'" c str

pchar ('a', "aaab") |> bind (printChar >> Success)

我经常使用这种模式,但是我向Result类型添加了第二个通用参数,该参数代表函数的eventseffects。通过允许我们拥有可以在代码中处理的特定Result事件(而不是检查特定的字符串),或者通过包含域事件(例如警告),这增加了Failure类型的实用性副作用以及我们的成功成果。

为此,我使用一种称为OperationResult的类型,以避免与内置的F#Result类型混淆。因为有时我会包括事件/结果成功的结果以及失败的结果,所以我为SuccessfulResult定义了一个类型,该类型既可以是返回值,也可以是返回值以及事件列表。总之,这提供了以下类型来定义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

如果要使用它,我将所有这些与一个名为operation的计算表达式一起打包在GitHub(和NuGet)上的库中。 GitHub页面上也有一些示例。

答案 2 :(得分:1)

@kagetoki是正确的,因为F#从用法中推断出类型。

此外,它看起来像一个解析器组合器库。 pchar的目的是尝试从输入中提取字符。如果成功,则返回字符和字符串的其余部分,自然的表示方法是char*string。它包装在Result中以支持失败,因此最终的返回类型应为:Result<char*string>