Выражения вычислений против аппликативных функторов, а что нет

Не совсем уверен, что заголовок описывает это нормально, но у меня есть примерно следующий код:

пакет.зависимости:

source https://www.nuget.org/api/v2
nuget fsharpx.extras
nuget mongodb.driver

некоторые.fsx:

#r @".\packages\MongoDB.Bson\lib\net45\MongoDB.Bson.dll"
#r @".\packages\MongoDB.Driver\lib\net45\MongoDB.Driver.dll"
#r @".\packages\MongoDB.Driver.Core\lib\net45\MongoDB.Driver.Core.dll"

#r @".\packages\FSharpX.Extras\lib\net45\FSharpX.Extras.dll"


open MongoDB
open MongoDB.Driver
open MongoDB.Bson 
open MongoDB.Bson.Serialization

open FSharpx.Choice

let private createClient (connectString:string) = MongoClient(connectString)
let CreateClient = protect createClient

let private getDb name (client:IMongoClient) = client.GetDatabase(name)
let GetDB1 name client =
    choose {
        let! c = client
        return! (protect (getDb name) c)
    }

let GetDB2 name (client:Choice<IMongoClient, exn>) =
    protect (getDb name)
    <!> client

Смысл этого «упражнения» заключался в том, чтобы написать GetDB2 так, чтобы он делал то же самое, что и GetDB1, но использовал операторы (аппликативы?), но в данный момент я не могу повернуть голову, чтобы справиться с этим.

Приведенный выше код компилируется, но подписи для GetDB1 и GetDB2 не равны, и я явно делаю что-то неправильно.

val GetDB1 :
  name:string ->
    client:Choice<#MongoDB.Driver.IMongoClient,exn> ->
      Choice<MongoDB.Driver.IMongoDatabase,exn>

val GetDB2 :
  name:string ->
    client:Choice<MongoDB.Driver.IMongoClient,exn> ->
      Choice<Choice<MongoDB.Driver.IMongoDatabase,exn>,exn>

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

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

Это, конечно, тоже может быть не совсем правильной идеей.

Может ли кто-нибудь указать мне некоторые направления для дальнейшего изучения, примеры кода или что-то еще? Любые комментарии любого типа на самом деле приветствуются на этом этапе ;-)

документ FSharpx

Дополнение

Я думаю, что следующее должно быть примерно таким же, как указано выше, но без зависимостей mongodb.

#r @".\packages\FSharpX.Extras\lib\net45\FSharpX.Extras.dll"

type DataBase = 
    {
        Name: string
    }

type Client = 
    {
        connectString: string
    } with member this.GetDatabase name = {
                        Name = name
                    }

open FSharpx.Choice
let private createClient (connectString:string) = {
  connectString= connectString
}

let CreateClient = protect createClient

let private getDb name (client:Client) = client.GetDatabase name

let GetDB1 name client =
    choose {
        let! c = client
        return! (protect (getDb name) c)
    }

let GetDB2 name client =
    protect (getDb name)
    <!> client

person Helge Rene Urholm    schedule 18.11.2016    source источник
comment
Не могли бы вы выразить это как MCVE? Мне не хочется возиться с MongoDB, чтобы исследовать этот вопрос...   -  person Mark Seemann    schedule 18.11.2016
comment
Кстати, в F# <!> часто используется вместо <$> в Haskell, который не является допустимым оператором в F#. Это просто инфиксная версия map (fmap в Haskell).   -  person Mark Seemann    schedule 18.11.2016
comment
@MarkSeemann, хе-хе. на самом деле это MCVE. или это так: не нужно возиться с монго здесь ;-) вышеприведенное выполняется без установленного mongodb или вообще каких-либо возни, если пакеты на месте. но я постараюсь сделать еще немного до кости MCVE ...   -  person Helge Rene Urholm    schedule 18.11.2016
comment
@MarkSeemann да, у меня есть такое понимание ‹!›, тогда аппликативный падеж больше ‹*› (я думаю). По этой причине я включил ссылку на документ FSharpX...   -  person Helge Rene Urholm    schedule 18.11.2016
comment
В Haskell Applicative определяется <*> и pure. AFAICT, в FSharpx.Choice это <*> и returnM.   -  person Mark Seemann    schedule 18.11.2016
comment
FWIW, наши последние комментарии пересеклись (я не видел ваш, когда писал свой). Я не пытаюсь проповедовать вам или что-то в этом роде...   -  person Mark Seemann    schedule 18.11.2016
comment
Я думаю, вам нужно что-то вроде (=<<) вместо (<!>).   -  person kvb    schedule 18.11.2016
comment
@kvb да, я хочу что-то вроде =‹‹ или ››=, но мне все еще не удается заставить это работать так, как я хочу...   -  person Helge Rene Urholm    schedule 18.11.2016
comment
@MarkSeemann, хе-хе, я тоже не собираюсь делать это похоже на вашу проповедь. как я сказал изначально, в данный момент я очень-очень рад, что кто-то пишет здесь какие-либо комментарии. ;-)   -  person Helge Rene Urholm    schedule 18.11.2016
comment
@kvb и еще несколько тестов, по-видимому, мне это удалось. MCVE от MarkSeeman, возможно, также помог мне... client ››= protect (имя getDb), кажется, помогает.... мне удалить этот вопрос? Или кто-то хочет на него ответить? Имеет ли этот вопрос какую-либо ценность? В любом случае спасибо обоим!   -  person Helge Rene Urholm    schedule 18.11.2016
comment
Вы бы сами ответили :)   -  person Mark Seemann    schedule 18.11.2016
comment
@MarkSeemann да, я мог бы. с точным кодом, дающим мне подпись, которую я хотел. ответ, который я действительно очень хочу, вероятно, больше похож на объяснение того, почему (››=) был здесь, на обзор общей идеи/кода и оценку передовой практики, а что нет ;-) так что несколько слишком широко, возможно... но это не я отвечал на него, так что не хочу его красть... еще раз спасибо!   -  person Helge Rene Urholm    schedule 18.11.2016
comment
Я понимаю. Не знаю, смогу ли я дать полезное объяснение, но >>= — это «просто» стандартный оператор bind, который вместе с return/pure определяет монаду. В выражениях вычислений F# привязки let! преобразуются в Bind.   -  person Mark Seemann    schedule 18.11.2016
comment
@MarkSeemann это? то же, что привязать? есть разница в составе этого рыбного клейсли и обычного бинда. Я думаю. но комментарии здесь не для такого типа дискуссий, я думаю ;-)   -  person Helge Rene Urholm    schedule 18.11.2016
comment
@MarkSeemann, ты прав, а я ошибаюсь. какой тоже набор всего этого изначально. вопросы и комментарии и все такое...   -  person Helge Rene Urholm    schedule 18.11.2016


Ответы (1)


Вы получаете соединение типов здесь, потому что вы использовали оператор <!>, который равен map. Это определяется примерно так:

let map f = function
    | Choice1Of2 value = Choice1Of2 (f value)
    | Choice2Of2 fail  = Choice2Of2 fail

Это имеет сигнатуру ('T -> 'U) -> Choice<'T,'Failure> -> Choice<'U,'Failure>, т.е. функция f используется как карта внутри типа choice. Например:

map (sprintf "%d")

имеет тип Choice<int, 'Failure> -> Choice<string, 'Failure>. Это хорошо для применения функций, которые не используют тип Choice — возможна только одна точка отказа, и это произошло до вызова map.

Ваша следующая функция, однако, создает тип Choice, но принимает тип, отличный от Choice. Это означает, что вы хотите, чтобы ошибки распространялись — если в значении есть ошибка, выберите ее. Если значение в порядке, но в функции есть ошибка, используйте это. Если все получилось, используйте это. Для этого требуется, чтобы два типа ошибок были одинаковыми, что для вас и есть (exn).

Здесь описывается операция bind, определенная следующим образом:

let bind f = function
    | Choice1Of2 value = f value
    | Choice2Of2 fail  = Choice2Of2 fail

с подписью ('T -> Choice<'U,'Failure>) -> Choice<'T,'Failure> -> Choice<'U,'Failure>.

Обратите внимание, что bind очень похоже на map, за исключением того, что последний преобразует результат в Choice1Of2 — сопоставленная функция всегда выполняется успешно.

В FSharpX вы можете получить доступ к bind с помощью оператора >>=, подобного |>, или оператора <<=, подобного <|.

Наконец, protect — это причудливый способ перехватить выброшенное исключение в Choice2Of2 exn. Он похож на map тем, что переданная функция имеет тип 'T -> 'U, но функция также может генерировать исключение, а переданный тип не является Choice. protect определяется примерно так:

let protect f x =
    try
        Choice1Of2 (f x)
    with
        exn -> Choice2Of2 exn

поэтому его подпись ('T -> 'U) -> 'T -> Choice<'U, exn>.

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


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

  • getDb name является функцией Client -> DataBase
  • protect (getDb name) — это функция Client -> Choice<DataBase, exn>
  • map (protect (getDb name)), следовательно, является функцией Choice<Client, exn> -> Choice<Choice<DataBase, exn>, 'Failure>, поскольку map работает внутри Choice.

Однако то, что вы хотите, это

let GetDB name client =
    bind (protect (getDb name)) client

или в операторной форме,

let GetDB name client = client >>= protect (getDb name)

В общем, если ваша функция сопоставления имеет сигнатуру 'T -> 'U, вам нужна map. Если у него 'T -> Choice<'U, 'Failure>, вам нужен bind.

person Jake Lishman    schedule 18.11.2016