Ранее задавал аналогичный вопрос, но почему-то я не нахожу выхода, пытаясь снова с другим примером.
Код в качестве отправной точки (немного урезанный) доступен по адресу https://ideone.com/zkQcIU.
(есть проблемы с распознаванием типа Microsoft.FSharp.Core.Result
, не знаю почему)
По сути, все операции должны быть конвейерными, при этом предыдущая функция передает результат следующей. Операции должны быть асинхронными, и они должны возвращать ошибку вызывающей стороне в случае возникновения исключения.
Требование состоит в том, чтобы дать вызывающему либо результат, либо ошибку. Все функции возвращают кортеж, заполненный либо Успехом type Article
, либо Отказом с объектом type Error
, имеющим описательные code
и message
, возвращенные с сервера.
Буду признателен за рабочий пример моего кода как для вызываемого, так и для вызывающего абонента в ответе.
Код вызываемого абонента
type Article = {
name: string
}
type Error = {
code: string
message: string
}
let create (article: Article) : Result<Article, Error> =
let request = WebRequest.Create("http://example.com") :?> HttpWebRequest
request.Method <- "GET"
try
use response = request.GetResponse() :?> HttpWebResponse
use reader = new StreamReader(response.GetResponseStream())
use memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(reader.ReadToEnd()))
Ok ((new DataContractJsonSerializer(typeof<Article>)).ReadObject(memoryStream) :?> Article)
with
| :? WebException as e ->
use reader = new StreamReader(e.Response.GetResponseStream())
use memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(reader.ReadToEnd()))
Error ((new DataContractJsonSerializer(typeof<Error>)).ReadObject(memoryStream) :?> Error)
Остальные связанные методы – одинаковая сигнатура и похожие тела. Фактически вы можете повторно использовать тело create
для update
, upload
и publish
, чтобы иметь возможность тестировать и компилировать код.
let update (article: Article) : Result<Article, Error>
// body (same as create, method <- PUT)
let upload (article: Article) : Result<Article, Error>
// body (same as create, method <- PUT)
let publish (article: Article) : Result<Article, Error>
// body (same as create, method < POST)
Код вызывающего абонента
let chain = create >> Result.bind update >> Result.bind upload >> Result.bind publish
match chain(schemaObject) with
| Ok article -> Debug.WriteLine(article.name)
| Error error -> Debug.WriteLine(error.code + ":" + error.message)
Изменить
На основе ответа и сопоставления его с реализацией Скотта (https://i.stack.imgur.com/bIxpD.png), чтобы облегчить сравнение и лучшее понимание.
let bind2 (switchFunction : 'a -> Async<Result<'b, 'c>>) =
fun (asyncTwoTrackInput : Async<Result<'a, 'c>>) -> async {
let! twoTrackInput = asyncTwoTrackInput
match twoTrackInput with
| Ok s -> return! switchFunction s
| Error err -> return Error err
}
Редактировать 2 На основе реализации привязки F#
let bind3 (binder : 'a -> Async<Result<'b, 'c>>) (asyncResult : Async<Result<'a, 'c>>) = async {
let! result = asyncResult
match result with
| Error e -> return Error e
| Ok x -> return! binder x
}