2017-01-15 3 views
5

Я пытаюсь применить свободный образец монады, как описан в F# for fun and profit для осуществления доступа к данным (для Microsoft Azure Table Storage)Free Монада в F # с общими выходными типа

Примера

Давайте предположим, что мы имеем три таблицы базы данных и три Dao в Foo, Bar, баз:

Foo   Bar   Baz 

key | col key | col key | col 
--------- --------- --------- 
foo | 1  bar | 2   | 

Я хочу, чтобы выбрать Foo с ключом = «Foo» и бар с ключом = «бар», чтобы вставить баз с ключом = «бАЗ» и цв = 3

Select<Foo> ("foo", fun foo -> Done foo) 
    >>= (fun foo -> Select<Bar> ("bar", fun bar -> Done bar) 
    >>= (fun bar -> Insert<Baz> ((Baz ("baz", foo.col + bar.col), fun() -> Done())))) 

В функции интерпретатора

  • Select приводит к вызову функции, которая принимает key : string и возвращает obj
  • Insert приводит к вызову функции, которая принимает obj и возвращает unit

Pro я проблема

Я определил две операции Select и Insert в дополнение к Done прекратить вычисления:

type StoreOp<'T> = 
    | Select of string * ('T -> StoreOp<'T>) 
    | Insert of 'T * (unit -> StoreOp<'T>) 
    | Done of 'T 

Для того, чтобы цепь StoreOp-х я пытаюсь реализовать правильную функцию связывания:

let rec bindOp (f : 'T1 -> StoreOp<'T2>) (op : StoreOp<'T1>) : StoreOp<'T2> = 
    match op with 
    | Select (k, next) -> 
     Select (k, fun v -> bindOp f (next v)) 
    | Insert (v, next) -> 
     Insert (v, fun() -> bindOp f (next())) 
    | Done t -> 
     f t 

    let (>>=) = bindOp 

Однако компилятор f # правильно предупреждает меня, что:

The type variable 'T1 has been constrained to be type 'T2 

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

Foo > Bar > unit 

все, что я могу выразить это:

Foo > Foo > Foo 

Как следует изменить определение StoreOp и/или bindOp для работы с разными типами вычислений?

+2

Я могу указать вам точную причину этой ошибки в коде 'bindOp', но основной причиной является ваш тип' StoreOp'. Если вы посмотрите на это внимательно, вы увидите, что он может только выражать цепочки операций одного типа. –

+1

Не удалось бы избежать всех этих уровней косвенности и сделать простой материал CRUD в чем-то вроде [сценария транзакций] (https://martinfowler.com/eaaCatalog/transactionScript.html)? Это похоже на то, что Томас Петричек описывает в последнем абзаце своего [ответа] (http://stackoverflow.com/a/41668459/467754). См. Также [Почему бесплатная Монада не является бесплатной] (https://www.youtube.com/watch?v=U0lK0hnbc4U). –

+0

Текущая реализация - это простой набор императивных функций CRUD. Пожалуйста, см. Комментарий ниже для мотивации. – dtornow

ответ

4

Как отметил Федор в комментариях, проблема связана с объявлением типа. Если вы хотите, чтобы сделать его компилировать по цене жертвуя типобезопасность, вы могли бы использовать obj в двух местах - это по крайней мере показывает, где проблема:

type StoreOp<'T> = 
    | Select of string * (obj -> StoreOp<'T>) 
    | Insert of obj * (unit -> StoreOp<'T>) 
    | Done of 'T 

Я не совсем уверен, что эти две операции Предположим, что модель - но я думаю, что Select означает, что вы что-то читаете (с помощью клавиши string?) и Insert означает, что вы сохраняете некоторое значение (а затем продолжаете unit). Итак, здесь данные, которые вы храните/читаете, будут obj.

Есть способы сделать этот тип безопасным, но я думаю, что вы получите лучший ответ, если объясните, чего вы пытаетесь достичь, используя монадическую структуру.

Не зная больше, я думаю, что использование свободных монадов сделает ваш код очень грязным и трудным для понимания. F # - это функционально-первый язык, что означает, что вы можете записывать преобразования данных в приятном функциональном стиле с использованием неизменяемых типов данных и использовать императивное программирование для загрузки ваших данных и хранения ваших результатов. Если вы работаете с хранилищем таблиц, почему бы просто не написать нормальный императивный код для чтения данных из хранилища таблиц, передать результаты в чисто функциональное преобразование и затем сохранить результаты?

+1

Благодарим вас за ответ. Чтобы ответить на ваш вопрос: я пытаюсь отделить чистый и нечистый код, как описано в [Чистота на нечистом языке] (http://blog.leifbattermann.de/2016/12/25/purity-in-an-impure-language -бесплатно-монада-крестики-нолики-CQRS-событийно-раздражающий /). Вы упомянули, что есть способы сделать безопасным «решение obj». Не могли бы вы поделиться тем, как вы это сделаете? – dtornow

+2

Я согласен с тем, что разделение чистого и нечистого кода желательно, но свободная монада - это ужасный способ сделать это. В конце концов, код все равно будет нечистным - то, что позволяет свободная монада, - это абстрагироваться от того, как именно обрабатывается примесь, и я думаю, что в этом нет никакой пользы. (Вы можете утверждать, что это полезно для тестирования, но я думаю, что это просто скрывает тестирование ключевой части, которую вы должны тестировать, и это операции с данными.) Если есть конкретная вещь, которую вы хотите достичь, тогда есть более способ сделать это. –

+0

Что касается безопасной версии типа, вы получите тип, который статически отслеживает типы всех чтений и записи таких «M >>>> >> 'Примерный проект, который использует это: http://blumu.github.io/ResumableMonad/ –