Czytałem artykuły z Wikipedii dotyczące programowania proceduralnego i programowania funkcjonalnego , ale nadal jestem nieco zdezorientowany. Czy ktoś mógłby sprowadzić to do rdzenia?
Czytałem artykuły z Wikipedii dotyczące programowania proceduralnego i programowania funkcjonalnego , ale nadal jestem nieco zdezorientowany. Czy ktoś mógłby sprowadzić to do rdzenia?
Odpowiedzi:
Język funkcjonalny (najlepiej) pozwala napisać funkcję matematyczną, tj. Funkcję, która przyjmuje n argumentów i zwraca wartość. Jeśli program jest wykonywany, funkcja ta jest logicznie oceniana w razie potrzeby. 1
Język proceduralny, z drugiej strony, wykonuje szereg kolejnych etapów. (Istnieje sposób na przekształcenie logiki sekwencyjnej w logikę funkcjonalną zwaną stylem przekazywania kontynuacji .)
W rezultacie czysto funkcjonalny program zawsze daje taką samą wartość dla danych wejściowych, a kolejność oceny nie jest dobrze zdefiniowana; co oznacza, że niepewne wartości, takie jak dane wejściowe użytkownika lub wartości losowe, są trudne do modelowania w czysto funkcjonalnych językach.
1 Jak wszystko inne w tej odpowiedzi, jest to uogólnienie. Ta właściwość, oceniająca obliczenia, gdy wynik jest potrzebny, a nie sekwencyjnie tam, gdzie się go nazywa, jest znana jako „lenistwo”. Nie wszystkie języki funkcjonalne są tak naprawdę leniwe, ani lenistwo nie ogranicza się do programowania funkcjonalnego. Podany tutaj opis stanowi raczej „mentalną strukturę” do myślenia o różnych stylach programowania, które nie są odrębnymi i przeciwnymi kategoriami, ale raczej płynnymi pomysłami.
Zasadniczo oba style są jak Yin i Yang. Jeden jest zorganizowany, a drugi chaotyczny. Są sytuacje, w których programowanie funkcjonalne jest oczywistym wyborem, a inne sytuacje, w których programowanie proceduralne jest lepszym wyborem. Właśnie dlatego istnieją co najmniej dwa języki, które niedawno wydały nową wersję, która obejmuje oba style programowania. ( Perl 6 i D 2 )
sub factorial ( UInt:D $n is copy ) returns UInt {
# modify "outside" state
state $call-count++;
# in this case it is rather pointless as
# it can't even be accessed from outside
my $result = 1;
loop ( ; $n > 0 ; $n-- ){
$result *= $n;
}
return $result;
}
int factorial( int n ){
int result = 1;
for( ; n > 0 ; n-- ){
result *= n;
}
return result;
}
(skopiowane z Wikipedii );
fac :: Integer -> Integer
fac 0 = 1
fac n | n > 0 = n * fac (n-1)
lub w jednej linii:
fac n = if n > 0 then n * fac (n-1) else 1
proto sub factorial ( UInt:D $n ) returns UInt {*}
multi sub factorial ( 0 ) { 1 }
multi sub factorial ( $n ) { $n * samewith $n-1 } # { $n * factorial $n-1 }
pure int factorial( invariant int n ){
if( n <= 1 ){
return 1;
}else{
return n * factorial( n-1 );
}
}
Faktyczny jest tak naprawdę częstym przykładem pokazującym, jak łatwo jest tworzyć nowe operatory w Perlu 6 w taki sam sposób, jak tworzy się podprogram. Ta funkcja jest tak wbudowana w Perl 6, że większość operatorów w implementacji Rakudo jest zdefiniowana w ten sposób. Umożliwia także dodawanie własnych wielu kandydatów do istniejących operatorów.
sub postfix:< ! > ( UInt:D $n --> UInt )
is tighter(&infix:<*>)
{ [*] 2 .. $n }
say 5!; # 120
W tym przykładzie pokazano również tworzenie zakresu ( 2..$n
) i metaoperator redukcji listy ( [ OPERATOR ] LIST
) w połączeniu z operatorem zwielokrotnienia liczby . ( *
)
Pokazuje również, że można wstawić --> UInt
podpis zamiast returns UInt
po nim.
(Możesz uciec od rozpoczęcia zakresu, 2
ponieważ powielony „operator” powróci, 1
gdy zostanie wywołany bez żadnych argumentów)
sub postfix:<!> ($n) { [*] 1..$n }
No operation can have side effects
Czy możesz to rozwinąć?
sub foo( $a, $b ){ ($a,$b).pick }
← nie zawsze zwraca to samo wyjście dla tego samego wejścia, podczas gdy następującesub foo( $a, $b ){ $a + $b }
Nigdy nie widziałem tej definicji podanej gdzie indziej, ale myślę, że dość dobrze podsumowuje podane tutaj różnice:
Programowanie funkcjonalne koncentruje się na wyrażeniach
Programowanie proceduralne koncentruje się na stwierdzeniach
Wyrażenia mają wartości. Program funkcjonalny to wyrażenie, którego wartością jest sekwencja instrukcji do wykonania przez komputer.
Instrukcje nie mają wartości i zamiast tego modyfikują stan niektórych maszyn koncepcyjnych.
W czysto funkcjonalnym języku nie byłoby instrukcji, w tym sensie, że nie ma sposobu na manipulowanie stanem (mogą nadal mieć konstrukcję składniową o nazwie „instrukcja”, ale gdyby nie manipulowała stanem, nie nazwałbym tego stwierdzeniem w tym sensie ). W czysto proceduralnym języku nie byłoby wyrażeń, wszystko byłoby instrukcją, która manipuluje stanem maszyny.
Haskell byłby przykładem czysto funkcjonalnego języka, ponieważ nie ma możliwości manipulowania stanem. Kod maszynowy byłby przykładem języka czysto proceduralnego, ponieważ wszystko w programie jest instrukcją, która manipuluje stanem rejestrów i pamięci maszyny.
Zagmatwane jest to, że zdecydowana większość języków programowania zawiera zarówno wyrażenia, jak i instrukcje, co pozwala mieszać paradygmaty. Języki można klasyfikować jako bardziej funkcjonalne lub proceduralne na podstawie tego, na ile zachęcają do używania wyrażeń a wyrażeń.
Na przykład C byłby bardziej funkcjonalny niż COBOL, ponieważ wywołanie funkcji jest wyrażeniem, podczas gdy wywołanie podprogramu w języku COBOL jest instrukcją (która manipuluje stanm wspólnych zmiennych i nie zwraca wartości). Python byłby bardziej funkcjonalny niż C, ponieważ pozwala wyrazić logikę warunkową jako wyrażenie przy użyciu oceny zwarcia (test && path1 || path2 w przeciwieństwie do instrukcji if). Schemat byłby bardziej funkcjonalny niż Python, ponieważ wszystko w schemacie jest wyrażeniem.
Nadal możesz pisać w funkcjonalnym stylu w języku, który zachęca do paradygmatu proceduralnego i odwrotnie. Po prostu trudniej i / lub bardziej niezręcznie jest pisać w paradygmacie, który nie jest zachęcany przez język.
W informatyce programowanie funkcjonalne jest paradygmatem programowania, który traktuje obliczenia jako ocenę funkcji matematycznych i unika stanu i zmiennych danych. Podkreśla zastosowanie funkcji, w przeciwieństwie do proceduralnego stylu programowania, który podkreśla zmiany stanu.
GetUserContext()
funkcji, kontekst użytkownika zostałby przekazany. Czy to funkcjonalne programowanie? Z góry dziękuję.
Uważam, że programowanie proceduralne / funkcjonalne / obiektywne dotyczy sposobu podejścia do problemu.
Pierwszy styl planuje wszystko krok po kroku i rozwiązuje problem, wdrażając jeden krok (procedurę) na raz. Z drugiej strony programowanie funkcjonalne kładłoby nacisk na podejście „dziel i rządź”, w którym problem jest podzielony na podproblem, następnie każdy podproblem jest rozwiązywany (tworzenie funkcji do rozwiązania tego podproblemu), a wyniki są łączone w celu utwórz odpowiedź na cały problem. Wreszcie, programowanie obiektywne naśladuje rzeczywisty świat poprzez stworzenie mini-świata wewnątrz komputera z wieloma obiektami, z których każdy ma (nieco) unikalne cechy i współdziała z innymi. Z tych interakcji wynikałby wynik.
Każdy styl programowania ma swoje zalety i wady. Dlatego robienie czegoś takiego jak „czyste programowanie” (tj. Czysto proceduralne - nikt nie robi tego, co jest dziwne - lub czysto funkcjonalne lub czysto obiektywne) jest bardzo trudne, jeśli nie niemożliwe, z wyjątkiem niektórych elementarnych problemów, szczególnie zaprojektowany w celu wykazania przewagi stylu programowania (stąd tych, którzy lubią czystość, nazywamy „weenie”: D).
Następnie, z tych stylów, mamy języki programowania, które zostały zaprojektowane tak, aby zoptymalizować je dla każdego stylu. Na przykład w Zgromadzeniu chodzi o procedury. Okej, większość wczesnych języków ma charakter proceduralny, nie tylko Asm, jak C, Pascal (i Fortran, słyszałem). Następnie wszyscy mamy znaną Javę w obiektywnej szkole (w rzeczywistości Java i C # są również w klasie zwanej „zorientowaną na pieniądze”, ale jest to przedmiotem kolejnej dyskusji). Również celem jest Smalltalk. W szkole funkcjonalnej mielibyśmy „prawie funkcjonalną” (niektórzy uważali ją za nieczystą) rodzinę Lisp i rodzinę ML oraz wiele „czysto funkcjonalnych” Haskell, Erlang itp. Nawiasem mówiąc, istnieje wiele języków ogólnych, takich jak Perl, Python Ruby.
num = 1
def function_to_add_one(num):
num += 1
return num
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
#Final Output: 2
num = 1
def procedure_to_add_one():
global num
num += 1
return num
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
#Final Output: 6
function_to_add_one
jest funkcją
procedure_to_add_one
to procedura
Nawet jeśli uruchomisz tę funkcję pięć razy, za każdym razem zwróci 2
Jeśli wykonasz tę procedurę pięć razy, pod koniec piątego przebiegu otrzymasz 6 .
Aby rozwinąć komentarz Konrada:
W rezultacie czysto funkcjonalny program zawsze daje taką samą wartość dla danych wejściowych, a kolejność oceny nie jest dobrze zdefiniowana;
Z tego powodu kod funkcjonalny jest ogólnie łatwiejszy do zrównoleglenia. Ponieważ (ogólnie) nie ma żadnych skutków ubocznych funkcji i (ogólnie) po prostu działają na podstawie ich argumentów, wiele problemów z współbieżnością znika.
Programowanie funkcjonalne jest również używane, gdy musisz być w stanie udowodnić, że Twój kod jest poprawny. Jest to o wiele trudniejsze w przypadku programowania proceduralnego (niełatwe w funkcjonalnym, ale wciąż łatwiejszym).
Oświadczenie: Nie korzystałem z programowania funkcjonalnego od lat i dopiero niedawno zacząłem się nim ponownie przyglądać, więc może nie mam tutaj całkowitej racji. :)
Jedną z rzeczy, których tak naprawdę nie zauważyłem, jest to, że współczesne języki funkcjonalne, takie jak Haskell, bardziej skupiają się na funkcjach pierwszej klasy do kontroli przepływu niż na jawnej rekurencji. Nie trzeba definiować silni rekurencyjnie w Haskell, jak to zostało zrobione powyżej. Myślę, że coś takiego
fac n = foldr (*) 1 [1..n]
jest idealną konstrukcją idiomatyczną i znacznie bliższą duchem użycia pętli niż wyraźnej rekurencji.
Języki proceduralne mają tendencję do śledzenia stanu (przy użyciu zmiennych) i mają tendencję do wykonywania jako sekwencja kroków. Czysto funkcjonalne języki nie śledzą stanu, używają niezmiennych wartości i mają tendencję do wykonywania jako szereg zależności. W wielu przypadkach status stosu wywołań zawiera informacje równoważne informacjom przechowywanym w zmiennych stanu w kodzie proceduralnym.
Rekurencja to klasyczny przykład programowania funkcjonalnego stylu.
Konrad powiedział:
W rezultacie czysto funkcjonalny program zawsze daje taką samą wartość dla danych wejściowych, a kolejność oceny nie jest dobrze zdefiniowana; co oznacza, że niepewne wartości, takie jak dane wejściowe użytkownika lub wartości losowe, są trudne do modelowania w czysto funkcjonalnych językach.
Kolejność oceny w czysto funkcjonalnym programie może być trudna (e) do uzasadnienia (szczególnie z lenistwem) lub nawet nieistotna, ale myślę, że powiedzenie, że nie jest dobrze zdefiniowane, sprawia, że brzmi to tak, jakbyś nie wiedział, czy twój program idzie pracować w ogóle!
Być może lepszym wyjaśnieniem byłoby, że przepływ sterowania w programach funkcjonalnych opiera się na tym, kiedy potrzebna jest wartość argumentów funkcji. Dobrą rzeczą jest to, że w dobrze napisanych programach stan staje się jawny: każda funkcja wymienia swoje dane wejściowe jako parametry zamiast arbitralnie mungującego stan globalny. Tak więc na pewnym poziomie łatwiej jest uzasadnić kolejność oceny w odniesieniu do jednej funkcji na raz . Każda funkcja może zignorować resztę wszechświata i skupić się na tym, co musi zrobić. Po połączeniu funkcje mają takie same [1] działanie, jak w oderwaniu.
... niepewne wartości, takie jak dane wprowadzane przez użytkownika lub wartości losowe, są trudne do modelowania w czysto funkcjonalnych językach.
Rozwiązaniem problemu wejściowego w czysto funkcjonalnych programach jest osadzenie imperatywnego języka jako DSL przy użyciu wystarczająco silnej abstrakcji . W imperatywnych (lub nie-czystych funkcjonalnych) językach nie jest to konieczne, ponieważ można „oszukiwać” i przekazywać stan w sposób niejawny, a kolejność oceny jest jawna (czy ci się to podoba, czy nie). Z powodu tego „oszukiwania” i wymuszonej oceny wszystkich parametrów dla każdej funkcji, w imperatywnych językach 1) tracisz możliwość tworzenia własnych mechanizmów kontroli przepływu (bez makr), 2) kod nie jest z natury bezpieczny i / lub równoległy do wątków domyślnie, 3) i wdrożenie czegoś takiego jak cofnięcie (podróż w czasie) wymaga starannej pracy (programista musi przechowywać przepis na odzyskanie starych wartości!), Podczas gdy czyste funkcjonalne programowanie kupuje wszystkie te rzeczy - i jeszcze kilka zapomniałeś - „za darmo”.
Mam nadzieję, że to nie brzmi jak gorliwość, chciałem tylko dodać trochę perspektywy. Programowanie imperatywne, a szczególnie mieszane programowanie paradygmatów w potężnych językach, takich jak C # 3.0, jest nadal całkowicie skutecznym sposobem na załatwienie sprawy i nie ma srebrnej kuli .
[1] ... chyba że w odniesieniu do wykorzystania pamięci (por. Foldl i foldl 'w Haskell).
Aby rozwinąć komentarz Konrada:
a kolejność oceny nie jest dobrze określona
Niektóre języki funkcjonalne mają tak zwaną Lazy Evaluation. Co oznacza, że funkcja nie jest wykonywana, dopóki nie jest potrzebna wartość. Do tego czasu sama funkcja jest przekazywana.
Językami proceduralnymi są krok 1 krok 2 krok 3 ... jeśli w kroku 2 powiesz dodać 2 + 2, zrobi to wtedy. W leniwej ocenie powiedziałbyś, że dodaj 2 + 2, ale jeśli wynik nigdy nie zostanie użyty, to nigdy nie doda.
Jeśli masz szansę, poleciłbym pobranie kopii Lisp / Scheme i wykonanie w niej kilku projektów. Większość pomysłów, które ostatnio stały się bandwagonami, zostały wyrażone w Lisp dekady temu: programowanie funkcjonalne, kontynuacje (jako zamknięcia), wyrzucanie elementów bezużytecznych, a nawet XML.
Byłby to dobry sposób na uzyskanie przewagi nad wszystkimi obecnymi pomysłami i jeszcze kilkoma innymi, takimi jak obliczenia symboliczne.
Powinieneś wiedzieć, do czego służy funkcjonalne programowanie, a do czego nie. To nie jest dobre na wszystko. Niektóre problemy najlepiej wyrazić w kategoriach skutków ubocznych, w których to samo pytanie daje różne odpowiedzi w zależności od tego, kiedy zostanie zadane.
@Creighton:
W Haskell jest funkcja biblioteki o nazwie produkt :
prouduct list = foldr 1 (*) list
lub po prostu:
product = foldr 1 (*)
więc silnia „idiomatyczna”
fac n = foldr 1 (*) [1..n]
byłoby po prostu
fac n = product [1..n]
Programowanie proceduralne dzieli sekwencje instrukcji i konstrukcje warunkowe na osobne bloki zwane procedurami, które są sparametryzowane na argumentach będących (niefunkcjonalnymi) wartościami.
Programowanie funkcjonalne jest takie samo, z tym wyjątkiem, że funkcje są pierwszorzędnymi wartościami, więc można je przekazywać jako argumenty do innych funkcji i zwracać jako wynik wywołania funkcji.
Zauważ, że programowanie funkcjonalne jest uogólnieniem programowania proceduralnego w tej interpretacji. Jednak mniejszość interpretuje „programowanie funkcjonalne” jako pozbawione skutków ubocznych, co jest zupełnie inne, ale nie ma znaczenia dla wszystkich głównych języków funkcjonalnych oprócz Haskell.
Aby zrozumieć różnicę, należy zrozumieć, że paradygmat „ojca chrzestnego” programowania proceduralnego i funkcjonalnego jest programowaniem imperatywnym .
Zasadniczo programowanie proceduralne jest jedynie sposobem konstruowania programów imperatywnych, w których podstawową metodą abstrakcji jest „procedura”. (lub „funkcja” w niektórych językach programowania). Nawet programowanie zorientowane obiektowo to po prostu inny sposób konstruowania programu imperatywnego, w którym stan jest enkapsulowany w obiekty, stając się obiektem o „aktualnym stanie”, a ponadto ten obiekt ma zestaw funkcji, metod i innych rzeczy, które pozwalają programista manipuluje lub aktualizuje stan.
Teraz, jeśli chodzi o programowanie funkcjonalne, istotą jego podejścia jest to, że określa, jakie wartości należy przyjąć i jak te wartości należy przenieść. (więc nie ma stanu ani danych zmiennych, ponieważ przyjmuje funkcje jako wartości pierwszej klasy i przekazuje je jako parametry do innych funkcji).
PS: zrozumienie każdego paradygmatu programistycznego powinno wyjaśnić różnice między nimi wszystkimi.
PSS: Ostatecznie paradygmaty programowania to po prostu różne podejścia do rozwiązywania problemów.
PSS: ta odpowiedź na quora ma świetne wyjaśnienie.
Żadna z odpowiedzi tutaj nie pokazuje idiomatycznego programowania funkcjonalnego. Rekurencyjna czynnikowa odpowiedź jest świetna do reprezentowania rekurencji w FP, ale większość kodu nie jest rekurencyjna, więc nie sądzę, aby ta odpowiedź była w pełni reprezentatywna.
Załóżmy, że masz tablicę ciągów, a każdy ciąg reprezentuje liczbę całkowitą taką jak „5” lub „-200”. Chcesz sprawdzić tę tablicę wejściową ciągów w swoim wewnętrznym przypadku testowym (przy użyciu porównania liczb całkowitych). Oba rozwiązania pokazano poniżej
arr_equal(a : [Int], b : [Str]) -> Bool {
if(a.len != b.len) {
return false;
}
bool ret = true;
for( int i = 0; i < a.len /* Optimized with && ret*/; i++ ) {
int a_int = a[i];
int b_int = parseInt(b[i]);
ret &= a_int == b_int;
}
return ret;
}
eq = i, j => i == j # This is usually a built-in
toInt = i => parseInt(i) # Of course, parseInt === toInt here, but this is for visualization
arr_equal(a : [Int], b : [Str]) -> Bool =
zip(a, b.map(toInt)) # Combines into [Int, Int]
.map(eq)
.reduce(true, (i, j) => i && j) # Start with true, and continuously && it with each value
Podczas gdy czysto funkcjonalne języki są na ogół językami badawczymi (ponieważ świat rzeczywisty lubi bezpłatne efekty uboczne), w prawdziwych językach proceduralnych w stosownych przypadkach stosowana będzie znacznie prostsza składnia funkcjonalna.
Zwykle jest to realizowane za pomocą zewnętrznej biblioteki, takiej jak Lodash , lub dostępnej w nowszych językach, takich jak Rust . Podnoszenia ciężkich programowania funkcjonalnego odbywa się za pomocą funkcji / pojęć takich jak map
, filter
, reduce
, currying
, partial
, ostatnie trzy z którego można patrzeć na dalsze zrozumienie.
Aby można go było używać w środowisku naturalnym, kompilator zwykle musi opracować sposób wewnętrznej konwersji wersji funkcjonalnej na wersję proceduralną, ponieważ narzut wywołania funkcji jest zbyt wysoki. Przypadki rekurencyjne, takie jak pokazana silnia, wykorzystają sztuczki, takie jak wywołanie ogona, aby usunąć użycie pamięci O (n). Brak efektów ubocznych pozwala funkcjonalnym kompilatorom wdrożyć && ret
optymalizację nawet wtedy, gdy .reduce
jest ona wykonywana jako ostatnia. Korzystanie z Lodash w JS oczywiście nie pozwala na żadną optymalizację, więc jest to hit wydajności (co zwykle nie jest problemem przy tworzeniu stron internetowych). Języki takie jak Rust będą optymalizować wewnętrznie (i mają takie funkcje, jak try_fold
pomoc w && ret
optymalizacji).