2010-03-05 21 views
15

Zastanawiam się nad sposobami wykorzystania systemu typu Haskella do wymuszenia modułowości w programie. Na przykład, jeśli mam aplikację internetową, jestem ciekawy, czy istnieje sposób na oddzielenie całego kodu bazy danych od kodu CGI z kodu systemu plików z czystego kodu.Używanie systemu typu Haskella do wymuszania modularności

Na przykład, ja envisioning monady DB, więc mogę napisać funkcje jak:

countOfUsers :: DB Int 
countOfUsers = select "count(*) from users" 

Chciałbym to być niemożliwe do wykorzystania efektów ubocznych innych niż te obsługiwane przez monady DB. Wyobrażam sobie monadę wyższego poziomu, która byłaby ograniczona do bezpośrednich procedur obsługi adresów URL i byłaby w stanie komponować połączenia z monadą DB i monadą IO.

Czy to możliwe? Czy to jest mądre?

Aktualizacja: skończyło się to osiągnąć z Scala zamiast Haskell: http://moreindirection.blogspot.com/2011/08/implicit-environment-pattern.html

Odpowiedz

13

jestem wyobrażając monady wyższego poziomu, które byłby ograniczony do bezpośrednich procedur obsługi adresów URL i będzie w stanie komponować połączenia z monadą DB i monadą IO.

Z pewnością można to osiągnąć i uzyskać bardzo silne statyczne gwarancje dotyczące rozdzielenia komponentów.

Najprościej chcesz monotonii IO z ograniczeniami. Używając czegoś w rodzaju techniki "taintingu", możesz utworzyć zestaw operacji we-wy podniesionych do prostego opakowania, a następnie użyć systemu modułów, aby ukryć podstawowe konstruktory typów.

W ten sposób będzie można uruchomić kod CGI tylko w kontekście CGI, a kod DB w kontekście DB. Istnieje wiele przykładów na temat Hackage.

Innym sposobem jest skonstruowanie interpretera dla działań, a następnie użycie konstruktorów danych do opisania każdej operacji podstawowej, jaką chcesz. Operacje powinny nadal tworzyć monadę i możesz użyć do-notacji, ale zamiast tego będziesz budował strukturę danych, która opisuje działania do wykonania, które następnie wykonujesz w kontrolowany sposób za pomocą interpretera.

Daje to prawdopodobnie więcej introspekcji, niż jest to potrzebne w typowych przypadkach, ale podejście to daje pełną moc niewidocznego kodu użytkownika przed jego wykonaniem.

+0

Dzięki, Don! To pierwsze rozwiązanie brzmi jak to, czego szukam. Czy wiesz, że jakieś specjalne pakiety, które używają tej techniki, lub dobre terminy do Google dla ("ograniczone IO Monada" nie pojawiły się wiele)? – Bill

+1

Dobry przykład koncepcji "taint monad", http://blog.sigfpe.com/2007/04/trivial-monad.html –

+0

Dzięki. Jeśli zdecyduję się użyć "skażonej monady" dla mojej monady DB, co mam zrobić, aby wyodrębnić dane z monady DB? Czy moja procedura obsługi HTTP musi używać transformatora Monada z DB? – Bill

4

Dzięki za to pytanie!

Zrobiłem trochę pracy w środowisku sieciowym klient/serwer, który wykorzystywał monady do rozróżniania różnych środowisk egzekucji. Najwyraźniej były to: po stronie klienta i po stronie serwera, ale także pozwalały na pisanie kodu po obu stronach: (który mógł działać zarówno na kliencie, jak i na serwerze, ponieważ nie zawierał żadnych specjalnych funkcji) i również asynchroniczna strona klienta, która została użyta do napisania kodu nieblokującego na kliencie (zasadniczo kontynuacja monady po stronie klienta). Brzmi to całkiem nierozerwalnie z twoim pomysłem na odróżnienie kodu CGI od kodu DB.

Oto niektóre zasoby o moim projekcie:

  • Slides z prezentacji, które zrobiłem o projekcie
  • Draft paper że pisałem z Don Syme
  • I ja też napisałem Bachelor thesis na ten temat (który jest dość długi)

Myślę, że jest to ciekawe podejście i może dać ci ciekawą gwarancję o kodzie. Jednak są pewne trudne pytania. Jeśli masz funkcję po stronie serwera, która pobiera int i zwraca int, jaki powinien być typ tej funkcji? W moim projekcie użyłem int -> int server (ale może to być również możliwe zastosowanie server (int -> int).

Jeśli masz kilka funkcji, takich jak ta, to nie jest tak proste, aby je komponować. Zamiast pisać goo (foo (bar 1)), trzeba napisać następujący kod:.

do b <- bar 1 
    f <- foo b 
    return goo f 

można napisać to samo za pomocą kilku kombinatorów, ale chodzi mi o to, że skład jest nieco mniej elegancki

+0

'import Control.Monad; (goo <= ephemient

+0

Tak, to było gorsze w F #, gdzie chciałem również napisać wywołania metod, takie jak 'o.Bar() .Foo(). Goo()' i nie ma sposobu, aby to zrobić za pomocą kombinatorów. Użycie '<= <' w Haskell wydaje się być w porządku, ale nadal nie jest doskonałe. –

5

myślę, że istnieje trzecia droga poza wspomniane dwa Don Stewart, która może być jeszcze prostsze:

class Monad m => MonadDB m where 
    someDBop1 :: String -> m() 
    someDBop2 :: String -> m [String] 

class Monad m => MonadCGI m where 
    someCGIop1 :: ... 
    someCGIop2 :: ... 

functionWithOnlyDBEffects :: MonadDB m => Foo -> Bar -> m() 
functionWithOnlyDBEffects = ... 

functionWithDBandCGIEffects :: (MonadDB m, MonadCGI m) => Baz -> Quux -> m() 
functionWithDBandCGIEffects = ... 

instance MonadDB IO where 
    someDBop1 = ... 
    someDBop2 = ... 

instance MonadCGI IO where 
    someCGIop1 = ... 
    someCGIop2 = ... 

Pomysł jest bardzo prosty, że można zdefiniować klasy typu dla różnych podzbiorów operacji chcesz oddzielić się, a następnie sparametryzuj swoje funkcje, korzystając z nich. Nawet jeśli jedyną konkretną monadą jaką kiedykolwiek wykonasz instancję klas jest IO, funkcje sparametryzowane na dowolnym MonadDB będą mogły korzystać tylko z operacji MonadDB (i tych zbudowanych z nich), więc osiągasz pożądany rezultat. I w funkcji "można zrobić wszystko" w Monadzie IO, można bezproblemowo korzystać z operacji MonadDB i MonadCGI, ponieważ IO jest instancją.

(Oczywiście, można określić inne przypadki, jeśli chcesz. Ones podnieść operacji poprzez różne transformatory monada byłaby prosta i myślę, że nie ma właściwie nic Cię powstrzymuje od przypadkach pisanie dla „wrapper” i "monter" tłumacza Don Stewart wspomina, łącząc w ten sposób podejścia - choć nie jestem pewien, czy jest jakiś powód, dla którego chciałbyś.)

Powiązane problemy