如何将(?)两个结果组合在一起



我开始玩f#,挑战自己写一个FizzBuzz (ikr,梦想大)。

看了几个Scott Wlaschin的演讲后,我试图通过使用所有这些很酷的地图和绑定等来重构我的混乱,但我发现自己被困在合并两个结果在一起。

我这里有这个函数(Result<int, string> * Result<int, string>) -> Result<int list, string>:

let rangeToList (from, ``to``) =
match from, ``to`` with
| Ok first, Ok last -> Ok [ first..last ]
| Error error, Ok _ -> Error error
| Ok _, Error error -> Error error
| Error error1, Error error2 -> Error(error1 + "; " + error2)

这里的结果是从string(控制台输入)解析int的结果。

我有一种感觉,这个符号可以简化,但我似乎不知道该怎么做。任何建议吗?

我想你已经接近最简单的方法了。我能想到的变通办法只有几个。

首先,我建议不要使用' to '作为变量名。这是可以做到的,但它看起来很丑!

其次,您可以将案例数量减少到仅三个-如果您首先匹配OK, OkError, Error案例,您知道您现在有一个Error和一个Ok,因此您可以使用|(或)模式加入那些并始终返回Error结果:

let rangeToList (from, last) =
match from, last with
| Ok first, Ok last -> Ok [ first..last ]
| Error error1, Error error2 -> Error(error1 + "; " + error2)
| Error error, _ | _, Error error -> Error error
第三,如果您将两个参数作为元组传递给函数,您可以使用function关键字进一步压缩,该关键字定义了一个立即匹配参数的函数(但如果参数是空格分隔的,这将不起作用):
let rangeToList = function
| Ok first, Ok last -> Ok [ first..last ]
| Error error1, Error error2 -> Error(error1 + "; " + error2)
| Error error, _ | _, Error error -> Error error

我认为这个解决方案很有效。如果您想使用高阶函数实现更聪明的功能(但我真的认为这里不需要),您可以定义一个zip函数,它接受两个结果并将它们组合起来——生成一对值并将所有错误收集到一个列表中:

module Result =
let zip r1 r2 = 
match r1, r2 with
| Ok v1, Ok v2 -> Ok (v1, v2)
| Error e1, Error e2 -> Error [e1; e2]
| Error e, _ | _, Error e -> Error [e]

使用这个作为帮助器,您现在可以将原始函数重写为:

let rangeToList2 (from, last) =
Result.zip from last 
|> Result.mapError (String.concat "; ")
|> Result.map (fun (first, last) -> [ first..last ])

我认为这是好的,但也许不必要的聪明。它并不比你的第一个版本短多少,当然也不容易理解。

您的代码很好,但我认为您所感知的模式被称为"应用程序"。应用程序就像单子的精简版,所以f#中重要的函数被称为MergeSources,而不是Bind。您可以使用它创建一个简单的计算构建器,如下所示:

type ResultBuilder() =
member _.BindReturn(result, f) =
result |> Result.map f
member _.MergeSources(result1, result2) =
match result1, result2 with
| Ok ok1, Ok ok2 -> Ok (ok1, ok2)
| Error error, Ok _
| Ok _, Error error -> Error error
| Error error1, Error error2 -> Error (error1 + "; " + error2)
let result = ResultBuilder()

然后您可以使用它来实现一个带有计算表达式的优雅版本的rangeToList,如下所示:

let rangeToList (from, ``to``) =
result {
let! first = from
and! last = ``to``
return [ first..last ]
}

这种方法的好处是它将MergeSources中的一般Result-争用逻辑与rangeToList中的特定领域逻辑分离开来。这允许您在其他域中自由地重用相同的result构建器。

更多细节在这里和这里。

最新更新