Цепочка вызовов REST в конвейере при управлении ошибками

Исходя из nodejs, где я мог связать асинхронные события, используя обещания, а затем оператор Я пытаюсь изучить, как все делается в идиоматическом F#.

Вызовы, которые я пытаюсь связать, представляют собой остаточные вызовы HTTP для некоторого объекта от создания до обновления и загрузки изображений для публикации.

Композиция функций говорит, что выходные данные одной функции должны совпадать с входными данными второй, и что общий ввод и вывод в моем случае будут string, то есть сериализованная строка JSON в качестве ввода и вывода всех этих функций.

Я узнал, что вы можете составлять функции, используя оператор >>. В идеале функции не должны выдавать ошибок, но с вводом-выводом случаются вещи, например, в этом случае, что если идентификатор объекта, который я пытаюсь создать, существует и т. д.

Неизвестно, и вопрос заключается в том, что произойдет, если во время цепочки последовательности произойдет ошибка, как вызывающий абонент узнает, что пошло не так, вместе с описательным сообщением? Операция может завершиться ошибкой в ​​середине, ближе к концу или прямо в начале последовательности цепочки.

Что я ожидаю от этих функций при ошибке, чтобы остановить выполнение цепочки и вернуть сообщение об ошибке вызывающей стороне. Сообщение об ошибке также представляет собой JSON string, поэтому между входными и выходными данными функции нет несовместимости.

Я просмотрел Choice тоже, но не уверен, что это то направление, в котором я должен двигаться.

Код не обязательно является полным, и все, что я ищу, - это направление для дальнейших исследований, чтобы получить ответы и, возможно, улучшить этот вопрос. Вот код для начала.

let create schema =
    // POST request
    """{"id": 1, "title": "title 1"}""" // result output

let update schema =
    // PUT request, update title
    """{"id": 1, "title": "title 2"}""" // output

let upload schema = 
    // PUT request, upload image and add thumbnail to json
    """{"id": 1, "title": "title 2", "thumbnail": "image.jpg"}""" 

let publish schema =
    // PUT request, publish the entity, add url for the entity
    if response.StatusCode <> HttpStatusCode.OK then
        """{"code": "100", "message": "file size above limit"}"""
    else
        """{"id": 1, "title": "title 2", "thumbnail": "image.jpg", "url": "http://example.com/1"}"""

let chain = create >> update >> upload >> publish

Редактировать — Попытка

Попытка параметризовать миниатюру изображения в части загрузки

let create (schema: string) : Result<string,string> =
    Ok """{"id": 1, "title": "title 1"}""" // result output

let update (schema: string) : Result<string,string>  =
    Ok """{"id": 1, "title": "title 2"}""" // output

let upload2 (img: string) (schema: string) : Result<string,string> =
    printf "upload image %s\n" img
    let statusCode = HttpStatusCode.OK
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg"}"""
    | x -> Error (sprintf "%A happened" x)

let publish (schema: string) =
    let statusCode = HttpStatusCode.InternalServerError
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg", "url": "http://example.com/1"}"""
    | _ -> Error """{"code": "100", "message": "couldn't publish, file size above limit"}"""

let chain = create >> Result.bind update >> Result.bind (upload2 "image.jpg") >> Result.bind publish

person Developer11    schedule 12.03.2018    source источник
comment
Промисы, а затем оператор на самом деле являются жалкой реализацией монадических вычислений (но для одного конкретного сценария) в JavaScript. Угадайте, откуда вообще взялась эта идея? :-)   -  person Fyodor Soikin    schedule 12.03.2018
comment
:) да, у него есть свои недостатки, кстати, это какая монада основана на данном ответе? Я читал, но все еще пытаюсь понять   -  person Developer11    schedule 12.03.2018
comment
Да, это и есть монада, более или менее.   -  person Fyodor Soikin    schedule 12.03.2018
comment
спасибо за подтверждение, то есть другими словами монада представляет собой конвейер функций, принимающих один ввод и возвращающих один вывод, и из-за ошибки на любом этапе управление возвращается вызывающей стороне с ошибкой или результатом в случае успеха?   -  person Developer11    schedule 12.03.2018
comment
Нет. То, что вы описали, является лишь частным случаем монады. Эта концепция более абстрактна, что позволяет выразить больше способов структурирования вычислений, которые просто рано возвращают ошибку, но также усложняет объяснение простыми словами.   -  person Fyodor Soikin    schedule 12.03.2018
comment
интересно... это приходит мне в голову как очень волшебная мистическая концепция, почти как культ :-) от тех парней из хаскелла. Я хочу углубиться в монады, используя F #, любые ссылки, пожалуйста   -  person Developer11    schedule 12.03.2018
comment
Начните с более простых вещей. Железнодорожное программирование — хорошее место для начала. Как только вы запомните несколько функциональных понятий, вы сможете устанавливать новые связи.   -  person Fyodor Soikin    schedule 12.03.2018
comment
Я добавил пример кода в приложение, пожалуйста, прокомментируйте и его.   -  person Developer11    schedule 12.03.2018
comment
Пожалуйста, не превращайте свой вопрос в доску обсуждений. Это не формат StackOverflow. Если у вас есть дополнительные вопросы, не стесняйтесь размещать их отдельно.   -  person Fyodor Soikin    schedule 12.03.2018


Ответы (1)


Хороший общий подход к этой проблеме заключается в заключении возвращаемых вами значений функций в тип Choice/Ether-like и использовании функции более высокого порядка для связывания их вместе, чтобы сбой распространяет/замыкает некоторые значимые данные. F# имеет тип Result с функцией bind, которую можно использовать следующим образом:

type MyResult = Result<string,string>
let f1 x : MyResult = printfn "%s" x; Ok "yep"
let f2 x : MyResult = printfn "%s" x; Ok "yep!"
let f3 x : MyResult = printfn "%s" x; Error "nope :("
let fAll = f1 >> Result.bind f2 >> Result.bind f3

> fAll "howdy";;
howdy
yep
yep!
[<Struct>]
val it : Result<string,string> = Error "nope :("

Первые две функции выполняются успешно, а третья терпит неудачу, поэтому вы возвращаете значение Error.

Также ознакомьтесь с этой статьей о программировании для железных дорог.

Обновите, чтобы быть более конкретным для вашего примера:

let create (schema: string) : Result<string,string> =
    Ok """{"id": 1, "title": "title 1"}""" // result output
let update (schema: string) : Result<string,string>  =
    Ok """{"id": 1, "title": "title 2"}""" // output
let upload (schema: string) = 
    let statusCode = HttpStatusCode.OK
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg"}"""
    | x -> Error (sprintf "%A happened" x)
let publish (schema: string) =
    let statusCode = HttpStatusCode.InternalServerError
    match statusCode with
    | HttpStatusCode.OK -> Ok """{"id": 1, "title": "title 2", "thumbnail": "image.jpg", "url": "http://example.com/1"}"""
    | _ -> Error """{"code": "100", "message": "file size above limit"}"""
let chain =
  create >> Result.bind update >> Result.bind upload >> Result.bind publish
person Taylor Wood    schedule 12.03.2018
comment
спасибо за направление, не могли бы вы добавить к ответу решение, используя вызовы моих функций, я пойму больше. Просто вы знаете, что я добавил условие ошибки в свою функцию публикации. - person Developer11; 12.03.2018
comment
здорово, спасибо, я очень ценю. Читая этот docs.microsoft.com/en-us/dotnet/ fsharp/language-reference/ также имело больше смысла. я скоро к вам вернусь - person Developer11; 12.03.2018
comment
Я исследовал и смотрел на это create >> Result.bind update, какое-нибудь объяснение на простом английском языке о том, что делает Result.bind update? Пожалуйста. - person Developer11; 12.03.2018
comment
@ Developer11 Result.bind принимает 1) функцию, которая возвращает Result<> и 2) Result<>. Если Result является Error, он просто возвращает его; если это Ok, то он выполняет заданную функцию и возвращает свой Result. - person Taylor Wood; 12.03.2018
comment
спасибо за объяснение, оно начинает показывать реальную картину, последнее, что я забыл, что функции загрузки требуется передать еще один аргумент, то есть путь к файлу эскиза изображения для загрузки, let upload (schema: string, image: string) как пользовательская параметризация может работать в таких процедурах цепочки? - person Developer11; 12.03.2018
comment
Тейлор, я попытался на основе вашего решения, пожалуйста, посмотрите мое редактирование. выражение функции upload2 и chain отличается, я пытаюсь выполнить каррирование upload2 при передаче миниатюры изображения. Пожалуйста, просмотрите, я не уверен, что это можно сделать на F #? - person Developer11; 13.03.2018
comment
в javascript я использовал для достижения этого с помощью замыканий или привязки функции с аргументом изображения, а затем передачи в цепочку, есть идеи? - person Developer11; 13.03.2018
comment
Я что-то исправил с параметрами, не могли бы вы повторить попытку еще раз. Нужны ваши благословения. - person Developer11; 13.03.2018
comment
@ Developer11 ваши обновления выглядят так, как будто они будут работать, конечно, есть только один способ узнать это :) - person Taylor Wood; 13.03.2018
comment
да, это работает :) Просто я прошу вашего благословения, если так и должно быть, когда дело доходит до идиоматического F#. Просто я привык делать что-то по-другому в javascript, где тот же эффект достигается с помощью bind и apply, и эти ключевые слова тоже есть в F #, просто интересно, есть ли лучший идиоматический способ, который я пропустил, я не просто хочу получить вещи сделано, но делается правильно. - person Developer11; 13.03.2018
comment
Привет, Тейлор, посмотри на это. stackoverflow.com/questions/49370853/ - person Developer11; 19.03.2018
comment
Тейлор, вы здесь, нужна ваша помощь, вопрос все еще не решен, и я могу сформулировать, пожалуйста, укажите мне направление применения программирования, ориентированного на железную дорогу, с асинхронными функциями. вы можете опубликовать ответ на связанный вопрос - person Developer11; 21.03.2018
comment
повторная попытка. stackoverflow.com/questions/49370853/ - person Developer11; 21.03.2018