Czy bazy danych i programowanie funkcjonalne są sprzeczne?

122

Od jakiegoś czasu jestem programistą WWW, a ostatnio zacząłem uczyć się programowania funkcjonalnego. Podobnie jak inni, miałem poważne problemy z zastosowaniem wielu z tych koncepcji w mojej pracy zawodowej. Dla mnie głównym powodem tego jest to, że widzę konflikt między celem FP, jakim jest pozostanie bezpaństwowcem, wydaje się zupełnie sprzeczne z faktem, że większość prac związanych z tworzeniem stron internetowych, które wykonałem, była mocno związana z bazami danych, które są bardzo skoncentrowane na danych.

Jedną rzeczą, która sprawiła, że ​​stałem się znacznie bardziej produktywnym programistą po stronie OOP, było odkrycie obiektów mapowania relacyjnego, takich jak MyGeneration d00dads dla .Net, Class :: DBI dla perla, ActiveRecord dla ruby ​​itp. To pozwoliło mi trzymać się z daleka. od pisania instrukcji wstawiania i wybierania przez cały dzień i skupiania się na łatwej pracy z danymi jako obiektami. Oczywiście nadal mogłem pisać zapytania SQL, gdy potrzebna była ich moc, ale poza tym było to ładnie abstrakcyjne za kulisami.

Teraz, przechodząc do programowania funkcjonalnego, wydaje się, że wiele frameworków internetowych FP, takich jak Links, wymaga napisania wielu standardowych kodów sql, jak w tym przykładzie . Weblocks wydaje się trochę lepszy, ale wydaje się, że używa pewnego rodzaju modelu OOP do pracy z danymi i nadal wymaga ręcznego napisania kodu dla każdej tabeli w twojej bazie danych, jak w tym przykładzie . Przypuszczam, że używasz generowania kodu do pisania tych funkcji mapujących, ale wydaje się to zdecydowanie nie do lispa.

(Uwaga: nie przyjrzałem się bardzo uważnie blokom stron internetowych ani łączom, może po prostu nie rozumiem, jak są one używane).

Tak więc pytanie brzmi: w przypadku części dostępu do bazy danych (które moim zdaniem są dość duże) aplikacji internetowej lub innych programów wymagających interfejsu z bazą danych sql, wydaje się, że jesteśmy zmuszeni przejść jedną z następujących ścieżek:

  1. Nie używaj programowania funkcyjnego
  2. Uzyskaj dostęp do danych w irytujący, nieabstrakcyjny sposób, który obejmuje ręczne pisanie dużej ilości kodu SQL lub podobnego do SQL ala Linki
  3. Zmusić nasz język funkcjonalny do paradygmatu pseudo-OOP, usuwając w ten sposób część elegancji i stabilności prawdziwego programowania funkcjonalnego.

Oczywiście żadna z tych opcji nie wydaje się idealna. Znalazłeś sposób na obejście tych problemów? Czy naprawdę jest jakiś problem?

Uwaga: Osobiście najlepiej znam LISP na froncie FP, więc jeśli chcesz podać jakieś przykłady i znać wiele języków FP, prawdopodobnie preferowanym językiem byłby lisp

PS: W przypadku problemów specyficznych dla innych aspektów tworzenia stron internetowych zobacz to pytanie .

Tristan Havelick
źródło
4
Sprawdź ClojureQL i HaskellDB. Są to warstwy abstrakcji wykorzystujące algebrę relacyjną.
Masse
10
Zaczynasz od złego założenia. Programowanie funkcjonalne polega na jawnym i zdrowym zarządzaniu stanem. W rzeczywistości bardzo dobrze współpracują z bazami danych.
Lucian
3
SQL jest jednym z najbardziej skutecznych języków skupiających się na programowaniu funkcjonalnym, nie sądzę, aby istniały w nim jakieś nieodłączne trudności.
Douglas
3
Twoje nr 2 i nr 3 to fałszywa dychotomia. Pisanie surowego SQL nie jest czymś, czego należy koniecznie unikać, a abstrakcje w bazie danych niekoniecznie muszą mieć charakter OOP.
Dan Burton

Odpowiedzi:

45

Przede wszystkim nie powiedziałbym, że CLOS (Common Lisp Object System) to „pseudo-OO”. Jest to pierwsza klasa OO.

Po drugie, uważam, że powinieneś użyć paradygmatu, który pasuje do twoich potrzeb.

Nie można przechowywać danych bezstanowych, podczas gdy funkcja jest przepływem danych i tak naprawdę nie potrzebuje stanu.

Jeśli masz kilka potrzeb połączonych, wymieszaj swoje paradygmaty. Nie ograniczaj się tylko do korzystania z prawego dolnego rogu skrzynki narzędziowej.

Svante
źródło
3
dla zabawy pomyślałem, że wspomnę o datalogu, który stara się być bardziej bezstanową bazą danych. rejestruje wszystkie działania, takie jak „użytkownik lubi post 1233”. Te akcje prowadzą do prawdziwego stanu bazy danych. Kluczem jest to, że zapytania to tylko fakty, a nie mutacje ...
Chet
80

Patrząc na to z perspektywy osoby zajmującej się bazą danych, uważam, że programiści front-end zbyt mocno starają się znaleźć sposoby, aby bazy danych pasowały do ​​ich modelu, zamiast rozważać najskuteczniejsze sposoby korzystania z bazy danych, która nie jest zorientowana obiektowo lub funkcjonalna, ale relacyjna i używa teoria mnogości. Widziałem, że generalnie powoduje to słaby kod. Ponadto tworzy kod, który jest trudny do dostrojenia.

Rozważając dostęp do bazy danych, należy wziąć pod uwagę trzy główne kwestie - integralność danych (dlaczego wszystkie reguły biznesowe powinny być egzekwowane na poziomie bazy danych, a nie za pośrednictwem interfejsu użytkownika), wydajność i bezpieczeństwo. SQL został napisany w celu efektywniejszego zarządzania dwoma pierwszymi zagadnieniami niż jakikolwiek język front-end. Ponieważ jest specjalnie do tego zaprojektowany. Zadanie bazy danych znacznie różni się od zadania interfejsu użytkownika. Czy można się zatem dziwić, że typ kodu, który jest najskuteczniejszy w zarządzaniu zadaniem, jest koncepcyjnie inny?

A bazy danych zawierają informacje krytyczne dla przetrwania firmy. Nic dziwnego, że firmy nie chcą eksperymentować z nowymi metodami, gdy stawką jest ich przetrwanie. Do diabła, wiele firm nie chce nawet uaktualnić do nowych wersji swojej istniejącej bazy danych. Tak więc w projektowaniu baz danych tkwi nieodłączny konserwatyzm. I to celowo w ten sposób.

Nie próbowałbym pisać T-SQL lub używać koncepcji projektowania baz danych do tworzenia interfejsu użytkownika, dlaczego miałbyś próbować używać języka interfejsu i koncepcji projektowych, aby uzyskać dostęp do mojej bazy danych? Ponieważ uważasz, że SQL nie jest wystarczająco wyszukany (lub nowy)? Albo nie czujesz się z tym dobrze? To, że coś nie pasuje do modelu, z którym czujesz się najbardziej komfortowo, nie oznacza, że ​​jest złe lub złe. Oznacza to, że jest inny i prawdopodobnie inny z uzasadnionego powodu. Używasz innego narzędzia do innego zadania.

HLGEM
źródło
„SQL został napisany w taki sposób, aby efektywniej zarządzać dwoma pierwszymi zagadnieniami niż jakikolwiek język interfejsu”. Naprawdę? Dlaczego więc jest tak, że ograniczenia SQL wciąż nie może robić takie rzeczy jak ta ?
Robin Green
Ale wyzwalacz może, to jest jeden z głównych celów wyzwalaczy, aby móc obsługiwać złożone ograniczenia.
HLGEM
2
Przesunąłbym Twój ostatni akapit, aby był akapitem wiodącym. Bardzo dobra uwaga, która odzwierciedla to, co również apelują inni, czyli podejście oparte na wielu paradygmatach (a nie jeden rozmiar dla wszystkich).
szkodnikowaty
31

Powinieneś spojrzeć na artykuł „Out of the Tar Pit” autorstwa Bena Moseleya i Petera Marksa, dostępny tutaj: „Out of the Tar Pit” (6 lutego 2006)

Jest to nowoczesny klasyk, który szczegółowo opisuje paradygmat / system programowania zwany programowaniem funkcjonalno-relacyjnym. Chociaż nie odnosi się bezpośrednio do baz danych, omawia, jak oddzielić interakcje ze światem zewnętrznym (na przykład bazami danych) od funkcjonalnego rdzenia systemu.

W artykule omówiono również sposób implementacji systemu, w którym stan wewnętrzny aplikacji jest definiowany i modyfikowany za pomocą algebry relacyjnej, co oczywiście wiąże się z relacyjnymi bazami danych.

Ten artykuł nie da dokładnej odpowiedzi na temat integracji baz danych i programowania funkcjonalnego, ale pomoże zaprojektować system tak, aby zminimalizować problem.

Kevin Albrecht
źródło
4
Co za wspaniały artykuł. Podany link jest uszkodzony (tymczasowo?), Ale artykuł znalazłem również na shaffner.us/cs/papers/tarpit.pdf
pestophagous
2
@queque Oryginalny link jest nadal martwy. Umieściłem nowe łącze w odpowiedzi Kevina.
David Tonhofer
25
  1. Języki funkcjonalne nie mają na celu pozostania bezpaństwowymi, ich celem jest jawne zarządzanie państwem. Na przykład w Haskell można uznać monadę stanu za serce „normalnego” stanu, a monadę IO za reprezentację stanu, który musi istnieć poza programem. Obie te monady pozwalają (a) jawnie reprezentować akcje stanowe i (b) budować akcje stanowe poprzez komponowanie ich przy użyciu referencyjnie przezroczystych narzędzi.

  2. Odwołujesz się do wielu ORMów, które, zgodnie z nazwą, są abstrakcyjnymi bazami danych jako zestawami obiektów. Doprawdy, nie to reprezentują informacje w relacyjnej bazie danych! Zgodnie ze swoją nazwą reprezentuje dane relacyjne. SQL jest algebrą (językiem) do obsługi relacji w relacyjnym zbiorze danych i sam w sobie jest całkiem „funkcjonalny”. Podnoszę to, aby wziąć pod uwagę, że (a) ORMy nie są jedynym sposobem mapowania informacji z bazy danych, (b) SQL jest w rzeczywistości całkiem przyjemnym językiem dla niektórych projektów baz danych oraz (c) że języki funkcjonalne często mają algebrę relacyjną odwzorowania, które ujawniają moc SQL w idiomatyczny (aw przypadku Haskella, sprawdzony typowo) sposób.

Powiedziałbym, że większość seplenienia to język funkcjonalny biedaka. Jest w pełni zdolny do używania zgodnie z nowoczesnymi praktykami funkcjonalnymi, ale ponieważ nie wymaga ich, społeczność jest mniej skłonna do ich używania. Prowadzi to do szeregu metod, które mogą być bardzo przydatne, ale z pewnością przesłaniają, jak czyste interfejsy funkcjonalne mogą nadal w znaczący sposób wykorzystywać bazy danych.

J. Abrahamson
źródło
15

Nie sądzę, aby bezpaństwowa natura języków FP stanowiła problem z łączeniem się z bazami danych. Lisp nie jest czysto funkcjonalnym językiem programowania, więc nie powinien mieć problemu ze stanem. A czysto funkcjonalne języki programowania, takie jak Haskell, mają sposoby radzenia sobie z danymi wejściowymi i wyjściowymi, które można zastosować do korzystania z baz danych.

Z twojego pytania wygląda na to, że twój główny problem polega na znalezieniu dobrego sposobu na wyodrębnienie danych opartych na rekordach, które otrzymujesz z bazy danych, do czegoś, co jest lisp-y (lisp-ish?) Bez konieczności pisania dużej ilości SQL kod. Wygląda to bardziej na problem z narzędziami / bibliotekami niż problem z paradygmatem językowym. Jeśli chcesz robić czyste FP, być może seplenienie nie jest dla ciebie odpowiednim językiem. Zwykłe lisp wydaje się bardziej polegać na integracji dobrych pomysłów z oo, fp i innych paradygmatów niż na czystym fp. Może powinieneś używać Erlanga lub Haskella, jeśli chcesz przejść czystą trasę FP.

Myślę, że pomysły „pseudo-oo” w seplenie też mają swoje zalety. Możesz je wypróbować. Jeśli nie pasują do sposobu, w jaki chcesz pracować z danymi, możesz spróbować utworzyć warstwę na wierzchu Weblocks, która pozwoli ci pracować z danymi tak, jak chcesz. To może być łatwiejsze niż samodzielne pisanie wszystkiego.

Zastrzeżenie: nie jestem ekspertem od Lispa. Interesuję się głównie językami programowania i bawiłem się Lisp / CLOS, Scheme, Erlang, Python i trochę Ruby. W codziennym życiu programistycznym wciąż jestem zmuszony do używania C #.

Mendelt
źródło
3
Erlang nie jest czystym FP według jakiejkolwiek definicji tego słowa. Piszesz erlang, tworząc wiele procesów (wszystkie działające równolegle), które wysyłają do siebie wiadomości, podobnie jak obiekty w, powiedzmy, smalltalk. Tak więc z perspektywy wysokiego poziomu może się to wydawać nieco dziwne i zdecydowanie ma stan. Jeśli powiększysz (do kodu działającego wewnątrz procesu), wygląda to bardziej funkcjonalnie, ale nadal nie jest czysto funkcjonalne, ponieważ możesz wysyłać wiadomości (a tym samym I / O itp.) Z dowolnego miejsca, a także przechowywać " zmienne globalne "(globalne dla procesu, wewnątrz czegoś zwanego"
dyktatem
@Amadiro "zdecydowanie ma stan". Oczywiście, że tak. Zawsze mamy państwo. Problemem nie jest stan, jest to stan zmienny . Dobra część „programowania funkcjonalnego” polega na pozbyciu się reprezentacji stanu, które są kruche (np. Instancje obiektów, które są zmieniane przez inne procesy, podczas gdy posiadamy do nich odniesienie, w sposób nietransakcyjny, aby dodać obrażenia do obrażeń). Erlang ma niezmienny stan, a więc spełnia kryteria programowania funkcjonalnego. A zatem: Bazy danych wszelkiego rodzaju nigdy nie stanowią problemu. Aktualizacje bazy danych są problemem (zobacz także paskudny assert w Prologu).
David Tonhofer
15

Jeśli twoja baza danych nie niszczy informacji, możesz pracować z nią w sposób funkcjonalny zgodny z wartościami programowania „czysto funkcjonalnego”, pracując w funkcjach całej bazy danych jako wartości.

Jeśli w czasie T baza danych stwierdza, że ​​"Bob lubi Suzie", a miałeś funkcję lubię, która zaakceptowała bazę danych i podobną, to dopóki możesz odzyskać bazę danych w czasie T, masz czysty program funkcjonalny, który zawiera bazę danych . na przykład

# Start: Time T
likes(db, "Bob")
=> "Suzie"
# Change who bob likes
...
likes(db "Bob")
=> "Alice"
# Recover the database from T
db = getDb(T)
likes(db, "Bob")
=> "Suzie"

Aby to zrobić, nie możesz nigdy wyrzucić informacji, których możesz użyć (co w praktyce oznacza, że ​​nie możesz wyrzucić informacji), więc Twoje potrzeby przechowywania będą rosły monotonnie. Ale możesz zacząć pracować z bazą danych jako liniową serią dyskretnych wartości, gdzie kolejne wartości są powiązane z poprzednimi poprzez transakcje.

Na przykład to główna idea stojąca za Datomic .

zwierzę
źródło
Miły. Nie wiedziałem nawet o Datomicu. Zobacz także: uzasadnienie dla Datomic .
David Tonhofer
12

Ani trochę. Istnieje gatunek baz danych znany jako „Funkcjonalne bazy danych”, z których Mnesia jest prawdopodobnie najbardziej dostępnym przykładem. Podstawową zasadą jest to, że programowanie funkcjonalne jest deklaratywne, więc można je zoptymalizować. Łączenie można zaimplementować za pomocą funkcji List Compression na trwałych kolekcjach, a optymalizator zapytań może zautomatyzować sposób implementacji dostępu do dysku.

Mnesia jest napisana w Erlang i istnieje co najmniej jeden framework sieciowy ( Erlyweb ) dostępny dla tej platformy. Erlang jest z natury równoległy do ​​modelu wątków typu „nic wspólnego”, więc pod pewnymi względami nadaje się do skalowalnych architektur.

ConcernedOfTunbridgeWells
źródło
1
Nie sądzę, żeby to było dobre rozwiązanie. Istnieją również obiektowe bazy danych, ale zazwyczaj chcesz połączyć się ze zwykłą, starą relacyjną bazą danych SQL.
jalf
4
Nadal występuje niezgodność impedancji z językami obiektowymi i bazami danych w podobny sposób, jak w przypadku niezgodności impedancji funkcjonalnego języka SQL.
ConcernedOfTunbridgeWells
1
@ConcernedOfTunbridgeWells Arbitralnie stwierdzę, że to niedopasowanie impedancji jest wytworem wyobraźni ludzi z młotkami, dla których wszystko jest gwoździami. Bardzo cienkie warstwy i znajomość SQL mogą elegancko przejść długą drogę, stąd jOOQ i podobne podkładki.
David Tonhofer
6

Baza danych to doskonały sposób na śledzenie stanu w bezstanowym interfejsie API. Jeśli subskrybujesz REST, Twoim celem jest napisanie bezstanowego kodu, który współdziała z magazynem danych (lub innym zapleczem), który śledzi informacje o stanie w przejrzysty sposób, tak aby Twój klient nie musiał tego robić.

Pomysł Object-Relational Mapper, w którym importujesz rekord bazy danych jako obiekt, a następnie modyfikujesz go, jest tak samo przydatny i przydatny w programowaniu funkcjonalnym, jak w programowaniu obiektowym. Jedynym zastrzeżeniem jest to, że programowanie funkcjonalne nie modyfikuje obiektu w miejscu, ale interfejs API bazy danych umożliwia modyfikowanie rekordu w miejscu. Przepływ kontroli Twojego klienta wyglądałby mniej więcej tak:

  • Zaimportuj rekord jako obiekt (interfejs API bazy danych może w tym momencie zablokować rekord),
  • Przeczytaj obiekt i gałąź na podstawie jego zawartości, jak chcesz,
  • Zapakuj nowy obiekt z wybranymi modyfikacjami,
  • Przekaż nowy obiekt do odpowiedniego wywołania interfejsu API, które aktualizuje rekord w bazie danych.

Baza danych zaktualizuje rekord wprowadzonymi zmianami. Programowanie czysto funkcjonalne może uniemożliwić ponowne przypisywanie zmiennych w zakresie programu , ale interfejs API bazy danych nadal może zezwalać na aktualizacje w miejscu.

Smażony Brice
źródło
5

Najwygodniej jest z Haskellem. Najbardziej znanym frameworkiem sieciowym Haskell (porównywalnym z Railsami i Django) jest Yesod. Wygląda na to, że ma całkiem fajny, bezpieczny dla typów, multi-backendowy ORM. Zajrzyj do rozdziału Wytrwałość w ich książce.

Honza Pokorny
źródło
0

Bazy danych i programowanie funkcjonalne można łączyć.

na przykład:

Clojure to funkcjonalny język programowania oparty na teorii relacyjnych baz danych.

               Clojure -> DBMS, Super Foxpro
                   STM -> TransactionMVCC
Persistent Collections -> db, table, col
              hash-map -> indexed data
                 Watch -> trigger, log
                  Spec -> constraint
              Core API -> SQL, Built-in function
              function -> Stored Procedure
             Meta Data -> System Table

Uwaga: w najnowszej specyfikacji spec2 bardziej przypomina RMDB. zobacz: spec-alpha2 wiki: Schema-and-select

Opowiadam się za: budowaniem relacyjnego modelu danych na szczycie mapy skrótów, aby osiągnąć kombinację zalet NoSQL i RMDB. W rzeczywistości jest to odwrotna implementacja posgtresql.

Duck Typing: Jeśli wygląda jak kaczka i kwacze jak kaczka, musi to być kaczka.

Jeśli model danych clojure jest taki jak RMDB, obiekty clojure, takie jak RMDB i manipulowanie danymi clojure, takie jak RMDB, clojure musi być RMDB.

Clojure to funkcjonalny język programowania oparty na teorii relacyjnych baz danych

Wszystko jest w RMDB

Wdrażaj relacyjny model danych i programowanie w oparciu o mapę skrótów (NoSQL)

Lin Pengcheng
źródło