Czy programowanie funkcjonalne zwiększa „lukę reprezentacyjną” między problemami a rozwiązaniami? [Zamknięte]

18

Ponieważ język maszynowy (np. 0110101000110101) Języki komputerowe ewoluowały w kierunku wyższych form abstrakcji, ogólnie ułatwiając zrozumienie kodu, gdy dotyczy on problemu. Asembler był abstrakcją nad kodem maszynowym, C był abstrakcją nad asemblerem itp.

Projektowanie obiektowe wydaje się bardzo dobrze pozwalać modelować problem pod względem obiektów, np. Problem systemu rejestracji na studia można modelować za pomocą Courseklasy, Studentklasy itp. Następnie, pisząc rozwiązanie w języku OO mamy podobne klasy, które przyjmują obowiązki i co na ogół jest pomocne przy projektowaniu, szczególnie do modularyzacji kodu. Jeśli dam ten problem 10 niezależnym zespołom, które rozwiążą go metodą OO, ogólnie 10 rozwiązań będzie miało wspólne klasy związane z problemem. Różnic może być wiele, kiedy zaczniesz się łączyć i interakcji tych klas, więc nie ma czegoś takiego jak „zerowa luka reprezentacyjna”.

Moje doświadczenie z programowaniem funkcjonalnym jest bardzo ograniczone (bez użycia w świecie rzeczywistym, tylko programy typu Hello World). Nie widzę, w jaki sposób takie języki pozwalają na łatwe mapowanie rozwiązań FP na problemy (z małą luką reprezentacyjną), tak jak robią to języki OO.

Rozumiem zalety FP w odniesieniu do programowania współbieżnego. Ale czy czegoś mi brakuje, czy FP nie polega na zmniejszeniu luki reprezentacyjnej (ułatwienie zrozumienia rozwiązań)?

Innym sposobem na zadanie tego pytania: czy kod FP 10 różnych zespołów rozwiązujących ten sam rzeczywisty problem ma wiele wspólnego?


Z Wikipedii na temat Abstrakcji (informatyka) (moje wyróżnienie):

Funkcjonalne języki programowania zwykle wykazują abstrakcje związane z funkcjami , takie jak abstrakcje lambda (przekształcenie terminu w funkcję jakiejś zmiennej), funkcje wyższego rzędu (parametry to funkcje), abstrakcja w nawiasie (przekształcenie terminu w funkcję zmiennej).

Luka reprezentacyjna mogłaby zostać potencjalnie zwiększona, ponieważ [niektórych] problemów w świecie rzeczywistym nie da się łatwo modelować za pomocą takich abstrakcji.


Innym sposobem, w jaki widzę zmniejszającą się lukę reprezentacyjną, jest prześledzenie elementów rozwiązania z powrotem do problemu. 0„S i 1s w kodzie maszynowym jest bardzo trudny do prześledzenia, podczas gdy Studentklasa jest łatwa do prześledzenia. Nie wszystkie klasy OO łatwo śledzą obszar problemów, ale wiele z nich.

Czy abstrakcje FP nie zawsze wymagają wyjaśnienia, aby dowiedzieć się, którą część problematycznej przestrzeni rozwiązują (oprócz problemów matematycznych )?OK - jestem dobry z tej strony. Po przeanalizowaniu wielu innych przykładów widzę, jak abstrakcje FP są bardzo jasne dla części problemu wyrażonej w przetwarzaniu danych.


Akceptowana odpowiedź na powiązane pytanie Czy można używać UML do modelowania programu funkcjonalnego? - mówi „Funkcjonalni programiści nie mają większego zastosowania do tworzenia diagramów”. Naprawdę nie dbam o to, czy jest to UML, ale zastanawiam się, czy abstrakty FP są łatwe do zrozumienia / komunikacji, jeśli nie ma powszechnie używanych diagramów (zakładając, że ta odpowiedź jest poprawna). Ponownie, mój poziom wykorzystania / zrozumienia FP jest trywialny, więc nie rozumiem potrzeby schematów na prostych programach FP.

Projekt OO ma poziomy abstrakcji dla funkcji / klasy / pakietu, z enkapsulacją (kontrola dostępu, ukrywanie informacji), co ułatwia zarządzanie złożonością. Są to elementy, które pozwalają łatwiej przejść od problemu do rozwiązania i wrócić.


Wiele odpowiedzi mówi o tym, jak analizy i projektowanie są wykonywane w FP w sposób analogiczny do OO, ale jak dotąd nikt nie przytacza niczego wysokiego poziomu (Paul zacytował kilka interesujących rzeczy, ale jest niski poziom). Wczoraj dużo ćwiczyłem w Google i znalazłem ciekawą dyskusję. Poniżej pochodzi z Refactoring Functional Programs autorstwa Simona Thompsona (2004) (wyróżnienie moje)

Przy projektowaniu systemu obiektowego przyjmuje się za pewnik, że projekt poprzedzi programowanie. Projekty będą pisane przy użyciu systemu takiego jak UML, który jest obsługiwany w narzędziach takich jak Eclipse. Początkujący programiści mogą nauczyć się projektowania wizualnego przy użyciu systemów takich jak BlueJ. Prace nad podobną metodologią programowania funkcjonalnego opisano w FAD: Analiza i projektowanie funkcjonalne , ale niewiele jest innych prac. Przyczyn może być wiele.

  • Istniejące programy funkcjonalne mają skalę, która nie wymaga projektowania. Wiele programów funkcjonalnych jest niewielkich, ale inne, takie jak Glasgow Haskell Compiler, są znaczne.

  • Programy funkcjonalne bezpośrednio modelują domenę aplikacji, przez co projektowanie nie ma znaczenia. Podczas gdy języki funkcjonalne zapewniają wiele potężnych abstrakcji, trudno argumentować, że zapewniają one wszystkie abstrakcje potrzebne do modelowania świata rzeczywistego.

  • Programy funkcjonalne są budowane jako ewoluująca seria prototypów.

W cytowanej powyżej rozprawie doktorskiej korzyści wynikające z zastosowania metodologii analizy i projektowania (ADM) są przedstawione niezależnie od paradygmatów. Podano jednak argument, że ADM powinny być zgodne z paradygmatem implementacji. Oznacza to, że OOADM działa najlepiej w programowaniu OO i nie jest dobrze stosowany w innym paradygmacie, takim jak FP. Oto świetny cytat, który, jak myślę, parafrazuje to, co nazywam luką reprezentacyjną:

można długo spierać się o to, który paradygmat zapewnia najlepsze wsparcie dla rozwoju oprogramowania, ale osiąga się najbardziej naturalny, wydajny i skuteczny pakiet programistyczny, gdy pozostaje się w obrębie jednego paradygmatu, od opisu problemu do wdrożenia i dostarczenia.

Oto zestaw diagramów zaproponowanych przez FAD:

  • diagramy zależności funkcji przedstawiające funkcję z tymi, których używa w swojej implementacji;
  • diagram zależności typów, który zapewnia tę samą usługę dla typów; i,
  • diagramy zależności modułów, które przedstawiają widoki architektury modułu systemu.

W sekcji 5.1 pracy FAD znajduje się studium przypadku, które jest systemem do automatyzacji produkcji danych związanych z ligą piłki nożnej. Wymagania są w 100% funkcjonalne, np. Wprowadzanie wyników piłki nożnej, tworzenie tabel ligowych, tabel wyników, tabel obecności, przenoszenie graczy między drużynami, aktualizowanie danych po nowych wynikach itp. Nie udokumentowano, jak FAD działa w celu rozwiązania niefunkcjonalnych wymagań , oprócz stwierdzenia, że ​​„nowa funkcjonalność powinna być dozwolona przy minimalnym koszcie”, co jest prawie niemożliwe do przetestowania.

Niestety, oprócz FAD, nie widzę żadnych współczesnych odniesień do języków modelowania (wizualnych), które byłyby proponowane dla FP. UML to kolejny paradygmat, więc powinniśmy o tym zapomnieć.

Fuhrmanator
źródło
1
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
wałek klonowy

Odpowiedzi:

20

Podstawowe dane mają taką samą strukturę w prawie każdym paradygmacie. Będziesz mieć a Student, Courseitp., Niezależnie od tego, czy jest to obiekt, struktura, zapis, czy cokolwiek innego. Różnica w OOP nie polega na strukturze danych, lecz na strukturze funkcji.

W rzeczywistości uważam, że programy funkcjonalne są znacznie bardziej dopasowane do tego, co myślę o problemie. Na przykład, aby zaplanować harmonogram studenta na następny semestr, myślisz o takich rzeczach, jak listy kursów, które student ukończył, kursy w programie studiów, kursy oferowane w tym semestrze, kursy, na które student ukończył warunki wstępne, kursy z czasami, które nie konflikt itp.

Nagle nie jest jasne, która klasa powinna utworzyć i przechowywać wszystkie te listy. Tym bardziej, gdy masz złożone kombinacje tych list. Musisz jednak wybrać jedną klasę.

W FP piszesz funkcje, które zabierają ucznia i listę kursów, i zwracasz przefiltrowaną listę kursów. Możesz zgrupować wszystkie takie funkcje razem w module. Nie musisz go łączyć z jedną klasą.

Dlatego modele danych wyglądają bardziej jak programiści OOP myślą o swoich modelach, zanim zostaną zanieczyszczeni klasami, które nie mają innego celu niż zapewnienie dogodnych miejsc do umieszczenia funkcji, które działają na kombinacjach innych klas. Żadnych dziwnych CourseStudentFilterListlub podobnych klas, których zawsze potrzebujesz w OOP, ale nigdy nie myśl o początku.

Karl Bielefeldt
źródło
5
@BenAaronson problem z mojego doświadczenia polega na tym, że wtedy każda funkcja związana z uczniem jest dodawana do klasy Ucznia, a jego obowiązki rosną i rosną, aż trzeba wprowadzić, StudentCourseFiltereraby utrzymać zarządzanie
AlexFoxGill
3
Podejścia @Den Hybrid mają swoje zalety. Nie jest jednak prawdą, że rozróżnienie jest sztuczne. Istnieje wiele kompromisów, które Scala musiała wprowadzić, aby dopasować OOP i FP (wnioskowanie o mniejszej mocy to jeden. Złożoność to kolejny: tak, język, który miesza OOP i FP, jest bardziej złożony - jak w „trudnym w użyciu” i rozumiem ”- niż jeden z jego czystszych braci z dwóch skrajności).
Andres F.,
3
@Den Zobacz także program Erik Meijera „Funkcjonalne” programowanie nie działa ” , co przemawia przeciwko podejściu hybrydowemu i dążeniu do pełnego„ fundamentalistycznego ”programu ramowego.
Andres F.,
4
@Den Popularność, którą opisujesz, jest, obawiam się, przypadkiem historii. Używam Scali w pracy i jest to złożona bestia ze względu na sposób, w jaki próbuje mieszać FP i OOP. Przejście na prawdziwy język FP, taki jak Haskell, to powiew świeżego powietrza - i nie mówię też z akademickiego punktu widzenia!
Andres F.,
3
@ Den Nie mogę powiedzieć, co robi jedna z tych funkcji. Mogę ci jednak powiedzieć, czy pierwsza funkcja w FP była sortowaniem, czy filtrem, to byłaby nazwana filter lub sort , a nie func(tak jak to nazwałeś IFilter, itp.). Ogólnie rzecz biorąc, w FP nazwałbyś to funclub fgdy albo jest sortalbo filterjest prawidłowym wyborem! Na przykład podczas pisania funkcji wyższego rzędu, która przyjmuje funcjako parametr.
Andres F.,
10

Kiedy wiele lat temu brałem udział w zajęciach z języka Java, oczekiwano, że pokażemy nasze rozwiązania całej klasie, więc mogłem zobaczyć, jak ludzie myślą; jak logicznie rozwiązują problemy. W pełni oczekiwałem, że rozwiązania skupią się wokół trzech lub czterech popularnych rozwiązań. Zamiast tego obserwowałem, jak 30 uczniów rozwiązało problem na 30 zupełnie różnych sposobów.

Oczywiście, gdy początkujący programiści zdobywają doświadczenie, zyskają kontakt z typowymi wzorcami oprogramowania, zaczną używać tych wzorców w kodzie, a następnie ich rozwiązania mogą się łączyć wokół kilku optymalnych strategii. Wzorce te tworzą język techniczny, za pomocą którego doświadczeni programiści mogą się komunikować.

Językiem technicznym leżącym u podstaw programowania funkcjonalnego jest matematyka . W związku z tym problemy, które najlepiej nadają się do rozwiązania przy użyciu programowania funkcjonalnego, są zasadniczo problemami matematycznymi. Z punktu widzenia matematyki nie mam na myśli matematyki, którą można zobaczyć w rozwiązaniach biznesowych, takich jak dodawanie i odejmowanie. Mówię raczej o matematyce, którą możesz zobaczyć w Math Overflow, lub matematyce, którą zobaczysz w wyszukiwarce Orbitza (jest napisana w Lisp).

Ta orientacja ma pewne istotne konsekwencje dla funkcjonalnych programistów próbujących rozwiązać rzeczywiste problemy programistyczne:

  1. Programowanie funkcjonalne jest bardziej deklaratywne niż imperatywne; dotyczy przede wszystkim powiedzenia komputerowi, co ma robić, a nie jak to zrobić.

  2. Programy obiektowe są często budowane od góry do dołu. Tworzony jest projekt klasy, a szczegóły są wypełniane. Programy funkcjonalne są często budowane od podstaw, zaczynając od małych, szczegółowych funkcji, które są łączone w funkcje wyższego poziomu.

  3. Programowanie funkcjonalne może mieć zalety w prototypowaniu złożonej logiki, konstruowaniu elastycznych programów, które mogą zmieniać się i ewoluować w sposób organiczny, oraz w tworzeniu oprogramowania, w którym początkowy projekt jest niejasny.

  4. Programy obiektowe mogą być bardziej odpowiednie dla domen biznesowych, ponieważ klasy, komunikaty między obiektami i wzorce oprogramowania zapewniają strukturę, która jest odwzorowana na domenę biznesową, przechwytuje jej inteligencję biznesową i dokumentuje ją.

  5. Ponieważ całe praktyczne oprogramowanie wywołuje efekty uboczne (I / O), czysto funkcjonalne języki programowania wymagają mechanizmu do wywoływania tych efektów ubocznych, pozostając matematycznie czystym (monady).

  6. Programy funkcjonalne można łatwiej sprawdzić ze względu na ich matematyczny charakter. System typowania Haskell może znaleźć w czasie kompilacji rzeczy, których nie potrafi większość języków OO.

I tak dalej. Podobnie jak w przypadku wielu rzeczy w informatyce, języki obiektowe i języki funkcjonalne mają różne kompromisy.

Niektóre współczesne języki OO przyjęły niektóre użyteczne koncepcje programowania funkcjonalnego, dzięki czemu możesz mieć to, co najlepsze z obu światów. Linq i funkcje językowe dodane w celu jego obsługi są tego dobrym przykładem.

Robert Harvey
źródło
6
@ thepacker: Masz stan w programowaniu funkcjonalnym. Jedynie reprezentacja jest inna: w OOP używasz zmiennych, podczas gdy w programowaniu funkcjonalnym możesz używać nieskończonych strumieni stanów, które są tworzone w locie, gdy program musi je sprawdzić. Niestety, stan często utożsamiany jest ze zmiennymi zmiennymi, tak jakby zmienne zmienne były jedynym sposobem reprezentacji stanu. W rezultacie wielu uważa, że ​​język programowania bez zmiennych zmiennych nie może modelować stanu.
Giorgio,
12
„W związku z tym problemy, które najlepiej nadają się do rozwiązania za pomocą programowania funkcjonalnego, są w istocie problemami matematycznymi.”: Nie zgadzam się z tym stwierdzeniem (patrz także mój poprzedni komentarz), przynajmniej nie rozumiem, dlaczego to powinno być prawdą lub co to jest właściwie znaczy. Możesz postrzegać przechodzenie przez strukturę katalogów jako problem matematyczny i implementować ją za pomocą funkcji rekurencyjnej, ale ponieważ klient nie widzi kodu źródłowego, rozwiązujesz tylko praktyczny problem, taki jak znalezienie pliku gdzieś w systemie plików. Uważam, że takie rozróżnienie między problemami matematycznymi i niematerialnymi jest sztuczne.
Giorgio
5
+1, ale nie jestem sprzedany za punkty 2 i 4. Widziałem wiele samouczków i postów na blogu Haskell, które zaczynają się od rozwiązania problemu odgórnego, definiowania wszystkich typów i operacji, aby zobaczyć, jak to wszystko pasuje do siebie, podczas gdy pozostawiając każdą wartość i definicję funkcji niezdefiniowaną (dobrze, zdefiniowaną do zgłaszania wyjątku). Wydaje mi się, że programiści Haskell mają tendencję do dokładniejszego modelowania problemu, ponieważ są bardziej skłonni do dodawania trywialnych typów ze względu na semantykę - np. Dodawania SafeStringtypu do reprezentowania ciągów znaków, którym uniknięto HTML - i generalnie śledzą efekty.
Doval
4
„problemy, które najlepiej nadają się do rozwiązania za pomocą programowania funkcjonalnego, są zasadniczo problemami matematycznymi”. To po prostu nieprawda. Fakt, że pojęcie monad wywodzi się z teorii kategorii, nie oznacza również, że zadania matematyczne są najbardziej naturalną / dopasowaną domeną dla języków funkcjonalnych. Oznacza to jedynie, że kod napisany w silnie typowanych językach funkcjonalnych może być opisany, analizowany i uzasadniony przy użyciu matematyki. Jest to potężna dodatkowa funkcja, a nie coś, co powstrzymuje Cię przed napisaniem „Barbie Horse Adventures” w Haskell.
itsbruce
6
@itsbruce Barbie Horse Adventures to poważna matematyczna symulacja najwyższego kalibru, to pidgeon wykrawający FP w zbyt skomplikowane projekcje numeryczne, takie jak matryce opisujące fizyczne migotanie włosów Barbie w dyskretnym okresie w różnych możliwych rzeczywistych środowiskach, które przekonują ludzi do całkowicie odrzucają to jako nieistotne dla ich codziennego kodowania problemów biznesowych.
Jimmy Hoffa
7

Chciałbym podkreślić aspekt, który uważam za ważny i który nie został uwzględniony w innych odpowiedziach.

Po pierwsze, myślę, że luka reprezentacyjna między problemami a rozwiązaniami może być bardziej w umyśle programisty, zgodnie z ich pochodzeniem i pojęciami, które są im bardziej znane.

OOP i FP patrzą na dane i operacje z dwóch różnych perspektyw i zapewniają różne kompromisy, jak już zauważył Robert Harvey.

Jednym z ważnych aspektów, które się różnią, jest sposób, w jaki pozwalają one na rozszerzenie twojego oprogramowania.

Rozważ sytuację, w której masz kolekcję typów danych i zbiór operacji, na przykład masz różne formaty obrazów i utrzymujesz bibliotekę algorytmów do przetwarzania obrazów.

Programowanie funkcjonalne ułatwia dodawanie nowych operacji do oprogramowania: potrzebujesz tylko lokalnej zmiany w kodzie, a mianowicie dodajesz nową funkcję, która obsługuje różne formaty danych wejściowych. Z drugiej strony, dodawanie nowych formatów jest bardziej zaangażowane: musisz zmienić wszystkie funkcje, które już zaimplementowałeś (zmiana nielokalna).

Podejście obiektowe jest do tego symetryczne: każdy typ danych przeprowadza własną implementację wszystkich operacji i odpowiada za wybór właściwej implementacji w czasie wykonywania (dynamiczne wysyłanie). Ułatwia to dodanie nowego typu danych (np. Nowego formatu obrazu): wystarczy dodać nową klasę i zaimplementować wszystkie jej metody. Z drugiej strony dodanie nowej operacji oznacza zmianę wszystkich klas, które muszą zapewnić tę operację. W wielu językach odbywa się to poprzez rozszerzenie interfejsu i dostosowanie wszystkich klas implementujących go.

To inne podejście do rozszerzenia jest jednym z powodów, dla których OOP jest bardziej odpowiedni w przypadku niektórych problemów, w których zestaw operacji zmienia się rzadziej niż zestaw typów danych, na których działają te operacje. Typowym przykładem są GUI: masz stały zestaw operacji, które musi implementować wszystkie widżety ( paint, resize, move, i tak dalej) i zbiór widżetów, które chcesz rozszerzyć.

Tak więc, zgodnie z tym wymiarem, OOP i FP to tylko dwa szczególne sposoby organizacji twojego kodu. Patrz SICP , w szczególności sekcja 2.4.3, tabela 2.22 i akapit Przekazywanie wiadomości .

Podsumowując: w OOP używasz danych do organizowania operacji, w FP używasz operacji do organizowania danych. Każde podejście jest silniejsze lub słabsze w zależności od kontekstu. Ogólnie rzecz biorąc, żaden z nich nie ma większej reprezentacyjnej luki między problemem a rozwiązaniem.

Giorgio
źródło
1
W rzeczywistości Pattern Visitor (w OOP) daje ci taką samą moc, jak powiedziałeś dla FP. Pozwala dodawać nowe operacje, a jednocześnie utrudniać dodawanie nowych klas. Zastanawiam się, czy możesz zrobić to samo w FP (np. Dodając nowe formaty zamiast operacji).
Euforyczny
1
Scenariusz przetwarzania obrazu nie wydaje się najlepszym przykładem. Z pewnością przekonwertowałbyś obrazy do jakiegoś wewnętrznego formatu i przetworzyłbyś je zamiast pisać osobne implementacje wszystkich elementów przetwarzania obrazu dla każdego typu obrazu?
Hej
1
@Giorgio, może nie rozumiem zamierzonego znaczenia z tej części: „Dodawanie nowych formatów jest bardziej zaangażowane: musisz zmienić wszystkie funkcje, które już zaimplementowałeś”.
Hej
2
@Hey Giorgio prawdopodobnie odnosi się do tak zwanego problemu ekspresji . „Dodawanie nowych formatów” oznacza „dodawanie nowych konstruktorów (inaczej„ przypadków ”) do typu danych”.
Andres F.,
1
@Euphoric: Nie zagłębiłem się w szczegóły, ale odpowiednik FP dla wzorca odwiedzającego powinien obejmować Otherwariant / konstruktor w Twoim typie danych oraz dodatkowy parametr funkcji, który należy przekazać wszystkim funkcjom w celu obsługi drugiego przypadku. Jest to oczywiście niewygodne / mniej naturalne w odniesieniu do rozwiązania OOP (dynamiczna wysyłka). W ten sam sposób wzór odwiedzających jest niewygodny / mniej naturalny niż rozwiązanie FP (funkcja wyższego rzędu).
Giorgio
5

Większość języków funkcjonalnych nie jest zorientowana obiektowo. Nie oznacza to, że nie mają żadnych obiektów (w sensie złożonych typów, które są powiązane z określoną funkcjonalnością). Haskell, podobnie jak java, ma Listy, Mapy, Tablice, wszelkiego rodzaju Drzewa i wiele innych złożonych typów. Jeśli spojrzysz na moduł Haskell List lub Map, zobaczysz zestaw funkcji bardzo podobnych do metod w większości równoważnych modułów bibliotecznych w języku OO. Jeśli przejrzysz kod, znajdziesz nawet podobne kapsułkowanie, z niektórymi typami (a dokładniej ich konstruktorami) i funkcjami, które mogą być używane tylko przez inne funkcje w module.

Sposób, w jaki pracujesz z tymi typami w Haskell, często niewiele różni się od sposobu OO. W Haskell mówię

  null xs
  length xs

a w Javie mówisz

  xs.isEmpty
  xs.length

Pomidor, pomidor. Funkcje Haskell nie są powiązane z obiektem, ale są ściśle związane z jego typem. Ach, ale , ¨ mówisz w Javie jest wywołaniem które kiedykolwiek długość metoda jest odpowiednia do rzeczywistego klasy tego obiektu. Niespodzianka - Haskell robi polimorfizm również z klasami typów (i innymi rzeczami).

W Haskell, jeśli mam to - zbiór liczb całkowitych - to nie ma znaczenia, czy kolekcja jest lista lub zestaw lub tablicy, ten kod

  fmap (* 2) is

zwróci kolekcję ze wszystkimi elementami podwójnymi. Polimorfizm oznacza, że zostanie wywołana odpowiednia funkcja mapy dla określonego typu.

Te przykłady nie są tak naprawdę złożonymi typami, ale to samo dotyczy trudniejszych problemów. Pomysł, że języki funkcjonalne nie pozwalają modelować złożonych obiektów i kojarzyć z nimi określoną funkcjonalność, jest po prostu błędny.

Jest to ważne i znaczące różnice między stylem i funkcjonalnej OO Do not ale myślę, że to odpowiedź musi radzić sobie z nimi. Pytałeś, czy języki funkcjonalne zapobiegają (czy utrudniają) intuicyjne modelowanie problemów i zadań. Odpowiedź brzmi nie.

itsbruce
źródło
W rzeczywistości możesz używać klas typów w Javie / C #; wystarczy jawnie przekazać słownik funkcji, zamiast kompilatora wnioskować o poprawnym na podstawie typu.
Doval
Och, zdecydowanie. Jak powiedział William Cook, wiele kodów OO faktycznie częściej korzysta z funkcji wyższego rzędu niż kod funkcjonalny, chociaż koder prawdopodobnie nie jest tego świadomy.
itsbruce
2
Dokonujesz fałszywego rozróżnienia. Lista nie jest bardziej obojętną strukturą danych niż stos, kolejka lub symulator kółko i krzyżyk. Lista może zawierać wszystko (w Haskell może to być częściowo zastosowane funkcje) i określa sposób interakcji z tymi rzeczami. Możesz zastosować listę pełną częściowo zastosowanych funkcji do innej listy wartości, a wynikiem będzie nowa lista zawierająca każdą możliwą kombinację funkcji z pierwszej i wprowadzoną z drugiej. To znacznie więcej niż struktura C.
itsbruce
3
Typy są używane do bardziej różnorodnych rzeczy w językach funkcjonalnych niż imperative / OO (systemy typów są z reguły bardziej wydajne i spójne, z jednej strony). Typy i ich funkcje są używane do kształtowania ścieżek kodu (dlatego na przykład kilka języków funkcjonalnych po prostu nie ma słów kluczowych dla pętli - niepotrzebnych).
itsbruce
4
@Fuhrmanator „Nie widzę, jak to się robi w FP” Więc narzekasz na coś, czego nie rozumiesz, nie ma sensu; uczcie się, rozumiejcie to dokładnie, a wtedy będzie to miało sens. Może po tym, gdy będzie to miało sens, zdecydujesz, że to bzdura i nie potrzebujesz innych, aby ci to udowodnić, chociaż inni ludzie tacy jak powyżej rozumieją to i nie doszli do takiego wniosku. Wygląda na to, że przyniosłeś nóż do walki z bronią, a teraz mówisz wszystkim, że pistolety są bezużyteczne, ponieważ nie są ostre.
Jimmy Hoffa
4

FP rzeczywiście dąży do zmniejszenia luki reprezentacyjnej:

W językach funkcjonalnych często spotyka się praktykę budowania języka (przy użyciu projektowania oddolnego) we wbudowanym języku specyficznym dla domeny (EDSL) . Pozwala to opracować sposób wyrażania obaw biznesowych w sposób naturalny dla domeny w języku programowania. Haskell i Lisp są z tego dumni.

Częścią (jeśli nie wszystkim) tego, co umożliwia tę umiejętność, jest większa elastyczność i ekspresja w obrębie samych podstawowych języków; dzięki funkcjom pierwszej klasy, funkcjom wyższego rzędu, składowi funkcji, aw niektórych językach, algebraicznym typom danych (związki dyskryminowane AKA) oraz możliwości definiowania operatorów *, istnieje większa elastyczność w wyrażaniu rzeczy niż w przypadku OOP, który oznacza, że ​​prawdopodobnie znajdziesz bardziej naturalne sposoby wyrażania rzeczy ze swojej problematycznej domeny. (Powinno powiedzieć coś, że wiele języków OOP ostatnio stosuje wiele z tych funkcji, jeśli nie zaczyna od nich).

* Tak, w wielu językach OOP możesz zastąpić standardowe operatory, ale w Haskell możesz zdefiniować nowe!

Z mojego doświadczenia w pracy z językami funkcjonalnymi (głównie F # i Haskell, trochę Clojure oraz trochę Lisp i Erlang), łatwiej jest mi mapować przestrzeń problemową na język niż z OOP - szczególnie z algebraicznymi typami danych, które uważam za bardziej elastyczne niż zajęcia. Coś, co rzuciło mnie na pętlę podczas rozpoczynania pracy z FP, szczególnie z Haskellem, polegało na tym, że musiałem myśleć / pracować na poziomie nieco wyższym niż byłem przyzwyczajony w językach imperatywnych lub OOP; możesz też na to wpaść.

Paweł
źródło
Obawy biznesowe (od klienta) nie są często wyrażane w funkcjach wyższego rzędu. Ale klient może napisać jednowierszową historię użytkownika i dostarczyć ci reguł biznesowych (lub możesz wyciągnąć je od klienta). Interesuje mnie (odgórne?) Modelowanie domen (z tych artefaktów), które pozwala nam uzyskać abstrakcje problemu odwzorowane na funkcje najwyższej klasy itp. Czy możesz podać jakieś odniesienia? Czy możesz użyć konkretnego przykładu domeny, abstrakcji itp.?
Fuhrmanator
3
@Fuhrmanator Klient może nadal pisać jednowierszowe historie użytkowników i reguły biznesowe, niezależnie od tego, czy korzystasz z OOP i FP . Nie oczekuję, że klienci zrozumieją funkcje wyższego rzędu, tak samo jak nie oczekuję, że zrozumieją dziedziczenie, skład lub interfejsy.
Andres F.
1
@Fuhrmanator Jak często klient wyrażał swoje obawy dotyczące generycznych Java lub abstrakcyjnych klas podstawowych i interfejsów? Drogi Boże. Paul udzielił bardzo dobrej odpowiedzi, wyjaśniając praktyczną i niezawodną metodę modelowania przy użyciu technik, które z pewnością nie są zbyt trudne do wizualizacji. A jednak nalegacie na to, aby opisać ją w kategoriach jednej konkretnej techniki modelowania dla jednego konkretnego paradygmatu, tak jakby to był w jakiś sposób najbardziej naturalny sposób reprezentowania projektu, a wszystko inne można przerobić na jego terminy.
itsbruce
@Fuhrmanator To może nie być tak wysoki poziom, jak chcesz, ale powinno dać trochę wglądu w proces projektowania za pomocą technik funkcjonalnych: Projektowanie z typami . Ogólnie polecam przeglądanie tej witryny, ponieważ zawiera ona doskonałe artykuły.
Paul
@Fuhrmanator Jest też seria Myślenie funkcjonalnie
Paul
3

Jak ujął to Niklaus Wirth: „Algorytmy + struktury danych = programy”. Programowanie funkcjonalne dotyczy sposobu organizowania algorytmów i nie mówi wiele o sposobach organizacji struktur danych. Rzeczywiście istnieją języki FP zarówno ze zmiennymi (Lisp), jak i niezmiennymi (Haskell, Erlang). Jeśli chcesz porównać i porównać FP z czymś, powinieneś wybrać programowanie imperatywne (C, Java) i deklaratywne (Prolog).

Z drugiej strony OOP jest sposobem na budowanie struktur danych i dołączanie do nich algorytmów. FP nie uniemożliwia budowania struktur danych podobnych do OOP. Jeśli mi nie wierzysz, spójrz na deklaracje typu Haskell. Jednak większość języków FP zgadza się, że funkcje nie należą do struktur danych i powinny raczej znajdować się w tej samej przestrzeni nazw. Odbywa się to w celu uproszczenia budowy nowych algorytmów poprzez składanie funkcji. Rzeczywiście, jeśli wiesz, jakie dane wejściowe pobiera funkcja i jakie generuje dane wyjściowe, dlaczego miałoby to mieć znaczenie, do której struktury danych należy? Na przykład, dlaczego addfunkcja musi być wywołana na instancji typu Integer i przekazała argument zamiast po prostu przekazać dwa argumenty Integer?

Dlatego nie rozumiem, dlaczego FP powinien utrudniać zrozumienie rozwiązań w ogóle, i nie sądzę, że i tak to robi. Należy jednak pamiętać, że doświadczony programista imperatywny z pewnością znajdzie programy funkcjonalne trudniejsze do zrozumienia niż programy imperatywne i na odwrót. Jest to podobne do dychotomii systemu Windows - Linux, gdy ludzie, którzy zainwestowali 10 lat w wygodne korzystanie ze środowiska Windows, mają trudności z Linuksem po miesiącu użytkowania, a ci, którzy są przyzwyczajeni do systemu Linux, nie mogą osiągnąć takiej samej wydajności w systemie Windows. Stąd „luka reprezentacyjna”, o której mówisz, jest bardzo subiektywna.

mkalkov
źródło
Z powodu enkapsulacji i ukrywania danych, które same w sobie nie są zasadami OO, nie do końca zgadzam się z tym, że OO to struktury danych + algorytmy (to tylko widok wewnętrzny). Piękno każdej dobrej abstrakcji polega na tym, że istnieje pewna usługa służąca rozwiązaniu części problemu, bez potrzeby rozumienia zbyt wielu szczegółów. Myślę, że mówisz o enkapsulacji, kiedy oddzielasz funkcje i dane w FP, ale jestem zbyt zielony, żeby to wiedzieć. Zredagowałem również moje pytanie, aby odwołać się do śledzenia od rozwiązania z powrotem do problemu (aby wyjaśnić znaczenie luki reprezentacyjnej).
Fuhrmanator,
if you know what input a function takes and what output it produces, why should it matter which data structure it belongs too?To brzmi jak spójność, która jest ważna dla każdego systemu modułowego. Twierdziłbym, że brak wiedzy na temat znalezienia funkcji utrudniłby zrozumienie rozwiązania, co wynika z większej luki reprezentacyjnej. Wiele systemów OO ma ten problem, ale jest to spowodowane złym projektem (niska kohezja). Znów problem przestrzeni nazw nie leży w mojej kompetencji w FP, więc może nie jest tak źle, jak się wydaje.
Fuhrmanator,
1
@Fuhrmanator Ale ty nie wiesz, gdzie znaleźć funkcję. I jest tylko jedna funkcja, a nie wiele funkcji o tej samej nazwie, zdefiniowanych dla każdego typu danych (ponownie, patrz „Problem z wyrażeniem”). Na przykład, w przypadku funkcji bibliotecznych Haskell (do których zachęca się Cię, zamiast ponownie odkrywać koło lub tworzyć nieco mniej przydatne wersje tych funkcji), masz wspaniałe narzędzia do odkrywania, takie jak Hoogle , który jest tak potężny, że pozwala wyszukiwać podpisem (spróbuj! :))
Andres F.
1
@ Fuhrmanator, po „To brzmi jak spójność”, spodziewałbym się, że logicznie wyciągniesz wniosek, że języki FP pozwalają pisać programy o większej spójności, ale mnie zaskoczyłeś. :-) Myślę, że powinieneś wybrać język FP, nauczyć się go i sam zdecydować. Gdybym był tobą, sugerowałbym zacząć od Haskell, ponieważ jest on dość czysty i dlatego nie pozwoli ci pisać kodu w trybie rozkazującym. Erlang i niektóre Lisp są również dobrym wyborem, ale łączą w sobie inne style programowania. Scala i Clojure, IMO, są dość skomplikowane na początku.
mkalkov