как я могу комбинировать/составлять вычислительные выражения в F#?

Это не для практической необходимости, а скорее для того, чтобы попытаться чему-то научиться.

Я использую выражение 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
    }

Он взаимодействует с библиотекой C#, которая обрабатывает все вызовы API CloudFlare и возвращает объект CloudFlareResult с флагом успеха, результатом и ошибкой.

Я переназначил этот тип на тип 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 (хотя я подозреваю, что это просто пользовательский код).

Опять же, это вопрос о том, чтобы что-то узнать, а не о практичности, поскольку это, вероятно, добавит больше кода, чем оно того стоит.


После комментария Гаса я понял, что было бы неплохо проиллюстрировать это более простым кодом:

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 и возвращать строку ошибки, но они разные. У обоих есть свои выражения, поэтому я могу связать их по мере необходимости. И исходный вопрос заключается в том, как их можно объединить вместе.


person Thomas    schedule 12.06.2021    source источник
comment
Невозможно объединить существующие построители выражений, можно только написать их с нуля. И хотя вы, безусловно, могли бы это сделать, на практике это почти никогда не стоит того. В данном конкретном случае ваш подход правильный.   -  person Fyodor Soikin    schedule 13.06.2021
comment
если вы не возражаете против расширенного SRTP foo, то есть способ объединить CE - проект FSharpPlus определяет некоторые monad-transformers - например, ResultT вы можете использовать для объединения некоторых CE с Result - если эти накладные расходы стоят для вас, я не знаю конечно   -  person Random Dev    schedule 13.06.2021
comment
Согласен, пока это единственная библиотека, которая позволяет это сделать. Я мог бы помочь вам, если вы опубликуете автономный код. В противном случае вот похожий вопрос   -  person Gus    schedule 13.06.2021
comment
как я указал в вопросе, для меня это тема обучения, поэтому накладные расходы/практичность не имеют значения; Я начал смотреть на библиотеку FSharpPlus, и это выглядит очень интересно. Монады/аппликативы для меня совершенно новые, и я хотел бы разобраться в них поглубже.   -  person Thomas    schedule 13.06.2021
comment
Именно потому, что это тема для изучения, возможно, лучше использовать более независимый образец кода. Таким образом, вы также увеличиваете шансы, что кто-то возьмет ваш код и покажет вам, как это сделать.   -  person Gus    schedule 13.06.2021
comment
@gus, вы правильно заметили, я отредактировал несколько простых псевдофункций.   -  person Thomas    schedule 14.06.2021
comment
Теперь, когда я вижу ваше обновление, похоже, вы не пытаетесь объединить 2 CE в смысле композиции, а скорее перегружаете операцию привязки CE, чтобы иметь дело с двумя разными типами, это то, что делает библиотека, такая как FsToolkit. , а в F#+ нет, и, поскольку он не следует никаким правилам, кроме удобства, я сомневаюсь, что вы сможете найти общий способ выразить это.   -  person Gus    schedule 14.06.2021
comment
@gus да, правильно; Я пытаюсь найти способ сделать гибкий CE, объединив операции привязки обоих. FsToolKit просто имеет специальный код для случаев, которые они поддерживают (и это очень полезно)   -  person Thomas    schedule 14.06.2021


Ответы (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
    }

person JaggerJo    schedule 20.06.2021