2017-01-15 9 views
5

Próbuję zastosować swobodny monada wzór, jak to opisano w F# for fun and profit do realizacji dostępu do danych (dla Microsoft Azure Table Storage)bezpłatny Monada w F # z wyjścia typu generic

Przykład

Załóżmy mamy trzy tabele bazy danych i trzy Dao za foo Bar, baz:

Foo   Bar   Baz 

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

Chcę wybrać foo with key = "foo" i bar z key = "bar", aby wstawić baz z key = "baz" i kol = 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())))) 

W funkcji tłumacza

  • Select powoduje wywołanie funkcji, które odbywają się key : string i zwraca obj
  • Insert wyniki wywołania funkcji, która pobiera obj i powraca unit

Pro blem

zdefiniowałem dwie operacje Select i Insert oprócz Done aby zakończyć obliczenia:

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

W celu łańcuch StoreOp na Staram się realizować poprawne działanie wiąże:

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 

Jednak kompilator f # prawidłowo ostrzega mnie, że:

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

Na tej realizacji bindOp typ jest stała w całym obliczeń, więc zamiast:

Foo > Bar > unit 

mogę wyrazić to:

Foo > Foo > Foo 

Jak należy zmodyfikować definicję StoreOp i/lub bindOp do pracy z różnymi typami w trakcie obliczeń?

+2

Mogę wskazać ci dokładną przyczynę tego błędu w kodzie "bindOp', ale podstawową przyczyną jest twój typ" StoreOp ". Jeśli przyjrzysz się temu uważnie, zobaczysz, że może tylko wyrazić łańcuch operacji na tym samym typie. –

+1

Czy nie byłoby możliwe uniknięcie wszystkich poziomów pośrednich i zrobienie prostych rzeczy w stylu CRUD w coś w rodzaju [Skryptu transakcyjnego] (https://martinfowler.com/eaaCatalog/transactionScript.html)? Jest to podobne do tego, co Tomas Petricek opisuje w ostatnim akapicie swojej [odpowiedzi] (http://stackoverflow.com/a/41668459/467754). Zobacz także [Dlaczego bezpłatna monada nie jest darmowa] (https://www.youtube.com/watch?v=U0lK0hnbc4U). –

+0

Obecna implementacja jest prostym zestawem imperatywnych funkcji CRUD. Zobacz motyw poniżej. – dtornow

Odpowiedz

4

Jak wspomniał Fiodor w komentarzu, problem dotyczy deklaracji typu. Jeśli chcesz, aby go skompilować za cenę utraty bezpieczeństwa typu, można użyć obj w dwóch miejscach - to przynajmniej pokazuje, gdzie jest problem:

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

Nie jestem do końca pewien, co te dwie operacje są powinien modelować - ale myślę, że Select oznacza, że ​​czytasz coś (z kluczem string?), a Insert oznacza, że ​​przechowujesz jakąś wartość (a następnie kontynuuj z unit). W tym przypadku dane, które przechowujesz/czytasz, to obj.

Są sposoby na zabezpieczenie tego typu, ale myślę, że otrzymasz lepszą odpowiedź, jeśli wyjaśnisz, co próbujesz osiągnąć, używając monadycznej struktury.

Nie wiedząc więcej, myślę, że używanie darmowych monad spowoduje, że twój kod będzie bardzo brudny i trudny do zrozumienia. F # jest funkcjonalnym językiem, co oznacza, że ​​można zapisać transformacje danych w przyjemnym stylu funkcjonalnym przy użyciu niezmiennych typów danych i używać imperatywnego programowania do ładowania danych i przechowywania wyników. Jeśli pracujesz z pamięcią tabel, dlaczego nie napisać normalnego kodu wymagającego odczytywania danych z pamięci tabeli, przekazać wyniki do czystej transformacji funkcjonalnej, a następnie zapisać wyniki?

+1

Dziękuję za odpowiedź. Aby odpowiedzieć na twoje pytanie: Próbuję oddzielić czysty i nieczysty kod, jak wyjaśniono w [Czystość w nieczystym języku] (http://blog.leifbattermann.de/2016/12/25/parent-in-in-an-impure-language -free-monad-tic-tac-toe-cqrs-event-souring /). Wspomniałeś, że istnieją sposoby, aby uczynić "rozwiązanie typu obj" bezpiecznym. Czy możesz podzielić się podejściem, które by to zrobiło? – dtornow

+2

Zgadzam się, że pożądane jest oddzielenie czystego i nieczystego kodu, ale wolna monada jest okropnym sposobem na to. W końcu kod i tak będzie nieczysty - to, co pozwala na to bezpłatna monada, to odejść od tego, jak dokładnie traktuje się nieczystość - i myślę, że nie ma w tym żadnej korzyści. (Można by argumentować, że jest to użyteczne do testowania, ale myślę, że po prostu przesłania testowanie kluczowej części, którą powinieneś testować i to jest operacja na danych.) Jeśli jest coś konkretnego, co chcesz osiągnąć, to jest lepsze sposób to zrobić. –

+0

Co się tyczy wersji bezpiecznej, otrzymasz typ, który statycznie śledzi typy wszystkich odczytów i zapisów takich 'M >>>> >> 'Przykładowy projekt, który wykorzystuje to: http://blumu.github.io/ResumableMonad/ –