如何在 F# 中组合计算表达式?

4
这并非出于实际需求,而是为了学习而进行的尝试。
我正在使用FSToolKit的 "asyncResult" 表达式,它非常方便,我想知道是否有一种方法可以“组合”这样的表达式,例如在此处组合异步和结果,还是必须编写自定义表达式?
以下是将IP设置为子域名的函数示例,使用了CloudFlare:
let setSubdomainToIpAsync zoneName url ip =

    let decodeResult (r: CloudFlareResult<'a>) =
        match r.Success with
        | true  -> Ok r.Result
        | false -> Error r.Errors.[0].Message

    let getZoneAsync (client: CloudFlareClient) =
        asyncResult {
            let! r = client.Zones.GetAsync()
            let! d = decodeResult r
            return!
                match d |> Seq.filter (fun x -> x.Name = zoneName) |> Seq.toList with
                | z::_ -> Ok z // take the first one
                | _    -> Error $"zone '{zoneName}' not found"
        }

    let getRecordsAsync (client: CloudFlareClient) zoneId  =
        asyncResult {
            let! r = client.Zones.DnsRecords.GetAsync(zoneId)
            return! decodeResult r
        }

    let updateRecordAsync (client: CloudFlareClient) zoneId (records: DnsRecord seq) =
        asyncResult {
            return!
                match records |> Seq.filter (fun x -> x.Name = url) |> Seq.toList with
                | r::_ -> client.Zones.DnsRecords.UpdateAsync(zoneId, r.Id, ModifiedDnsRecord(Name = url, Content = ip, Type = DnsRecordType.A, Proxied = true))
                | []   -> client.Zones.DnsRecords.AddAsync(zoneId, NewDnsRecord(Name = url, Content = ip, Proxied = true))
        }

    asyncResult {
        use client   = new CloudFlareClient(Credentials.CloudFlare.Email, Credentials.CloudFlare.Key)
        let! zone    = getZoneAsync client
        let! records = getRecordsAsync client zone.Id
        let! update  = updateRecordAsync client zone.Id records
        return! decodeResult update
    }

它与处理所有对CloudFlare API的调用并返回包含成功标识、结果和错误的CloudFlareResult对象的C#库进行接口连接。
我将该类型重新映射为Result<'a, string>类型:
let decodeResult (r: CloudFlareResult<'a>) =
    match r.Success with
    | true  -> Ok r.Result
    | false -> Error r.Errors.[0].Message

我可以为此编写一个表达式(假设我一直在使用它们,但尚未编写自己的表达式),但如果有一个asyncCloudFlareResult表达式,甚至是一个asyncCloudFlareResultOrResult表达式,那将是很好的。我想知道是否有一种机制将表达式组合在一起,就像FSToolKit所做的那样(尽管我怀疑那里只是自定义代码)。再次强调,这是一个学习的问题,而不是实用性问题,因为这可能会增加更多的代码而得不偿失。
根据Gus的评论,我意识到用一些更简单的代码来说明问题可能会更好:
function DoA : int -> Async<AWSCallResult<int, string>>
function DoB : int -> Async<Result<int, string>>

AWSCallResultAndResult {
    let! a = DoA 3
    let! b = DoB a
    return b
}

在这个例子中,我最终会得到两种类型,它们都可以接受一个整数并返回一个错误字符串,但它们是不同的。它们都有自己的表达式,所以我可以根据需要将它们链接在一起。 原始问题是关于如何将它们组合在一起的。

2
没有办法将现有的表达式构建器组合在一起,只能从头开始编写一个。虽然你当然可以这样做,但实际上几乎不值得。在这种特殊情况下,你的方法是正确的。 - Fyodor Soikin
2
如果您不介意一些高级的SRTP技巧,那么有一种方法可以组合CE - FSharpPlus项目确实定义了一些单子变换器 - 例如ResultT,您可以使用它来将一些CE与Result组合 - 如果这种开销对您来说是值得的,我当然不知道。 - Random Dev
1
正如我在问题中所指出的,这是一个对我来说很有学习意义的话题,因此额外的开销/实用性并不重要;我已经开始研究FSharpPlus库,它看起来非常有趣。Monad / Applicative 对我来说还很新颖,我希望能深入了解它们。 - Thomas
正因为这是一个学习的主题,也许最好使用更中立的示例代码。这样你也增加了别人拿起你的代码并向你展示如何做的机会。 - Gus
@gus 是的,正确的;我正在尝试找到一种方法,通过结合两者的绑定操作来制作一个灵活的CE。FsToolKit似乎只针对它们支持的情况有特定的代码(并且非常有用)。 - Thomas
显示剩余3条评论
1个回答

1
可以使用重载来扩展CEs。
下面的示例使得可以在常规结果构建器中使用“CustomResult”类型。

open FsToolkit.ErrorHandling

type CustomResult<'T, 'TError> =
    { IsError: bool
      Error: 'TError
      Value: 'T }

type ResultBuilder with

    member inline _.Source(result : CustomResult<'T, 'TError>) =
        if result.IsError then
            Error result.Error
        else
            Ok result.Value

let computeA () = Ok 42
let computeB () = Ok 23
let computeC () =
    { CustomResult.Error = "oops. This went wrong"
      CustomResult.IsError = true
      CustomResult.Value = 64 }

let computedResult =
    result {
        let! a = computeA ()
        let! b = computeB ()
        let! c = computeC ()

        return a + b + c
    }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接