【问题标题】:how can I combine / compose computation expressions, in F#?如何在 F# 中组合/组合计算表达式?
【发布时间】:2021-06-12 20:39:25
【问题描述】:

这不是为了实际需要,而是为了尝试学习一些东西。

我正在使用 FSToolKit 的 asyncResult 表达式,它非常方便,我想知道是否有一种方法可以“组合”表达式,例如此处的 async 和 result,或者自定义表达式有要写吗?

这是我使用 CloudFlare 将 ip 设置为子域的函数示例:

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
    }

它与一个 C# 库接口,该库处理对 CloudFlare API 的所有调用并返回一个 CloudFlareResult 对象,该对象具有成功标志、结果和错误。

我将该类型重新映射为 Result 类型:

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
}

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

【问题讨论】:

  • 现有的表达式构建器没有办法组合,只能从头写一个。虽然你当然可以这样做,但实际上它几乎不值得。在这种特殊情况下,您的方法是正确的。
  • 如果您不介意一些高级 SRTP foo,那么有一种方法可以组合 CE - FSharpPlus 项目确实定义了一些 monad-transformers - 例如 ResultT you可以用来将一些 CE 与 Result 结合起来 - 如果这个开销对你来说是否值得,我当然不知道
  • 我同意,到目前为止,它是唯一允许您这样做的库。如果您发布自包含的代码,我可以为您提供帮助。否则这里是similar question
  • 正如我在问题中指出的那样,这对我来说是一个学习主题,因此开销/实用性无关紧要;我已经开始研究 FSharpPlus 库,这看起来很有趣。 Monads / Applicatives 对我来说很新,我想深入了解它们
  • 正是因为它是一个学习主题,所以使用更不可知的示例代码可能会更好。这样一来,您还可以增加有人获取您的代码并向您展示如何操作的机会。

标签: f# computation-expression


【解决方案1】:

可以通过重载扩展 CE。

下面的示例可以将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
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多