我正在关注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>
:
返回类型应为Result<'a>
吗?
答案 0 :(得分:5)
Result<'a>
是 generic 类型,具有'a
作为类型参数。当您编写Success (charToMatch, remaining)
时,编译器会将此通用类型参数推断为char
和string
的元组: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
类型添加了第二个通用参数,该参数代表函数的events
或effects
。通过允许我们拥有可以在代码中处理的特定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>
。