Postanowiłem podjąć się nauki programowania funkcjonalnego. Do tej pory to był świetny wybuch, a ja „widziałem światło”. Niestety, tak naprawdę nie znam funkcjonalnego programisty, z którego mógłbym odrzucić pytania. Przedstawiamy Stack Exchange.
Biorę udział w kursie tworzenia aplikacji internetowych, ale mój instruktor nie zna programowania funkcjonalnego. Nie przeszkadza mi, że go używam, i właśnie poprosił mnie, aby pomóc mu zrozumieć, jak to działa, aby mógł lepiej czytać mój kod.
Zdecydowałem, że najlepszym sposobem na to jest zilustrowanie prostej funkcji matematycznej, takiej jak podniesienie wartości do potęgi. Teoretycznie mógłbym to łatwo zrobić za pomocą wstępnie zbudowanej funkcji, ale to by pomogło celowi przykładu.
W każdym razie mam trudności z ustaleniem, jak utrzymać wartość. Ponieważ jest to programowanie funkcjonalne, nie mogę zmienić zmiennej. Gdybym miał to bezwzględnie zakodować, wyglądałoby to tak:
(Poniżej znajduje się cały pseudokod)
f(x,y) {
int z = x;
for(int i = 0, i < y; i++){
x = x * z;
}
return x;
}
W programowaniu funkcjonalnym nie byłem pewien. Oto co wymyśliłem:
f(x,y,z){
if z == 'null',
f(x,y,x);
else if y > 1,
f(x*z,y-1,z);
else
return x;
}
Czy to jest poprawne? z
W obu przypadkach muszę zachować wartość, ale nie byłem pewien, jak to zrobić w programowaniu funkcji. Teoretycznie sposób, w jaki to zrobiłem, działa, ale nie byłem pewien, czy to było „właściwe”. Czy jest na to lepszy sposób?
y
.Odpowiedzi:
Przede wszystkim gratuluję „ujrzenia światła”. Uczyniłeś świat oprogramowania lepszym miejscem, poszerzając swoje horyzonty.
Po drugie, szczerze mówiąc, nie ma mowy, żeby profesor, który nie rozumie programowania funkcjonalnego, nie byłby w stanie powiedzieć nic użytecznego o twoim kodzie, poza banalnymi komentarzami, takimi jak „wcięcie się nie zgadza”. Nie jest to zaskakujące na kursie tworzenia stron internetowych, ponieważ większość tworzenia stron internetowych odbywa się za pomocą HTML / CSS / JavaScript. W zależności od tego, jak bardzo zależy ci na nauce tworzenia stron internetowych, możesz zechcieć nauczyć się narzędzi, których naucza twój profesor (choć może to być bolesne - wiem z doświadczenia).
Aby odpowiedzieć na zadane pytanie: jeśli twój imperatywny kod korzysta z pętli, istnieje prawdopodobieństwo, że kod funkcjonalny będzie rekurencyjny.
Zauważ, że ten algorytm jest mniej więcej identyczny z kodem rozkazującym. W rzeczywistości można uznać powyższą pętlę za cukier syntaktyczny dla iteracyjnych procesów rekurencyjnych.
Na marginesie, w rzeczywistości nie ma potrzeby używania
z
ani kodu imperatywnego, ani funkcjonalnego. Powinieneś napisać swoją funkcję imperatywną tak:zamiast zmieniać znaczenie zmiennej
x
.źródło
pow
nie jest w porządku. W tej chwilipow 3 3
zwraca81
zamiast27
. Powinno byćelse x * pow x (y-1).
To naprawdę tylko dodatek do odpowiedzi ogrodnika, ale chciałbym podkreślić, że istnieje nazwa dla wzorca, który widzisz: składanie.
W programowaniu funkcjonalnym fold jest sposobem na połączenie szeregu wartości, które „zapamiętują” wartość między każdą operacją. Zastanów się nad koniecznością dodania listy liczb:
Bierzemy listę wartości
xs
oraz stanu początkowego z0
(reprezentowaną przeztotal
w tym przypadku). Następnie dla każdejx
INxs
, łączy się tę wartość z obecnego stanu Według niektórych łączące działanie (w tym przypadku dodatkowo) i wykorzystać wynik jako nowego stanu. W istociesum_all([1, 2, 3])
jest równoważne z(3 + (2 + (1 + 0)))
. Ten wzorzec można wyodrębnić do funkcji wyższego rzędu , która akceptuje funkcje jako argumenty:Ta implementacja
fold
jest nadal niezbędna, ale można ją również wykonać rekurencyjnie:Wyrażona jako krotnie, twoja funkcja jest po prostu:
... gdzie
repeat(x, n)
zwraca listęn
kopiix
.Wiele języków, szczególnie tych ukierunkowanych na programowanie funkcjonalne, oferuje składanie w swojej standardowej bibliotece. Nawet Javascript zapewnia to pod nazwą
reduce
. Ogólnie rzecz biorąc, jeśli używasz rekurencji do „zapamiętywania” wartości w jakiejś pętli, prawdopodobnie potrzebujesz fold.źródło
fold(repeat(base, power), 1, *)
scan
jest zasadniczo miejscem, wfold
którym zamiast po prostu połączyć listę wartości w jedną wartość, jest on łączony, a każda wartość pośrednia jest wyrzucana z powrotem po drodze, tworząc listę wszystkich stanów pośrednich utworzonych przez złożenie zamiast po prostu generować stan końcowy. Jest możliwy do wdrożenia pod względemfold
(każda operacja zapętlania jest).combiner_func
jest to argument, asum_all
jest przepuszczanie anonimową funkcję (tolambda
bit - tworzy wartość funkcji bez nazywania go), który określa, jak ona chce połączyć dwa elementy razem.To jest dodatkowa odpowiedź, która pomaga wyjaśnić mapy i fałdy. W poniższych przykładach użyję tej listy. Pamiętaj, że ta lista jest niezmienna, więc nigdy się nie zmieni:
Użyję liczb w moich przykładach, ponieważ prowadzą one do łatwego do odczytania kodu. Pamiętaj jednak, że fałdy mogą być używane do wszystkiego, do czego może być używana tradycyjna pętla imperatywna.
mapa pobiera listę coś i funkcję i zwraca listę, która została zmodyfikowana za pomocą funkcji. Każdy element jest przekazywany do funkcji i staje się tym, co funkcja zwraca.
Najłatwiejszym tego przykładem jest dodanie numeru do każdego numeru na liście. Użyję pseudokodu, aby był niezależny od języka:
Jeśli wydrukowałeś
numbers2
, zobaczysz,[3, 4, 5, 6, 7]
która jest pierwsza lista z 2 dodanymi do każdego elementu. Zauważ, że funkcjaadd-two
została przekazanamap
użyta.Fold s są podobne, z tym wyjątkiem, że funkcja, którą musisz im nadać, musi przyjąć 2 argumenty. Pierwszym argumentem jest zwykle akumulator (w lewej części, które są najczęściej). Akumulator to dane przekazywane w pętli. Drugi argument to bieżący element listy; podobnie jak powyżej dla
map
funkcji.Jeśli wydrukowałeś
sum
, zobaczysz sumę listy liczb: 15.Oto argumenty do
fold
zrobienia:Jest to funkcja, którą dajemy fold. W folderze przejdzie funkcja bieżącego akumulatora i bieżąca pozycja na liście. Cokolwiek zwróci funkcja, stanie się nowym akumulatorem, który zostanie przekazany do funkcji następnym razem. W ten sposób „zapamiętujesz” wartości, gdy zapętlasz styl FP. Dałem mu funkcję, która pobiera 2 liczby i dodaje je.
To jest akumulator początkowy; co akumulator zaczyna, jak przed przetworzeniem jakichkolwiek pozycji na liście. Kiedy sumujesz liczby, jaka jest suma, zanim dodasz jakieś liczby razem? 0, które podałem jako drugi argument.
Wreszcie, podobnie jak w przypadku mapy, przekazujemy również listę liczb do przetworzenia.
Jeśli fałdy nadal nie mają sensu, rozważ to. Kiedy piszesz:
Zasadniczo umieszczasz przekazaną funkcję między każdą pozycją na liście i dodajesz akumulator początkowy do lewej lub prawej (w zależności od tego, czy jest to lewy czy prawy pas), więc:
Staje się:
Co równa się 15.
Użyj a,
map
jeśli chcesz zamienić jedną listę na inną, o tej samej długości.Użyj a,
fold
gdy chcesz zmienić listę w pojedynczą wartość, na przykład sumując listę liczb.Jak zauważył @Jorg w komentarzach, „pojedyncza wartość” nie musi być czymś prostym jak liczba; może to być dowolny pojedynczy obiekt, w tym lista lub krotka! Sposób, w jaki faktycznie kliknąłem dla siebie foldery, polegał na zdefiniowaniu mapy w kategoriach fold. Zwróć uwagę, jak akumulator jest listą:
Szczerze mówiąc, kiedy zrozumiesz każdy, zdasz sobie sprawę, że prawie każdą pętlę można zastąpić fałdem lub mapą.
źródło
i
nigdy się nie definiuje), ale myślę, że masz dobry pomysł. Jednym z problemów w twoim przykładzie jest: funkcja (x
), przekazywana jest tylko jeden element listy na raz, a nie cała lista. Przy pierwszymx
wywołaniu przekazuje on początkowy akumulator (y
) jako pierwszy argument, a pierwszy element jako drugi argument. Przy następnym uruchomieniux
przekaże nowy akumulator po lewej stronie (cokolwiekx
zwrócił za pierwszym razem), a drugi element listy jako drugi argument.fold
gdy chcesz zmienić listę w jedną wartość (np. Sumując listę liczb)”. - Chcę tylko zauważyć, że ta „pojedyncza wartość” może być dowolnie złożona… łącznie z listą! W rzeczywistościfold
jest to ogólna metoda iteracji, która może zrobić wszystko , co może zrobić iteracja. Np.map
Może być trywialnie wyrażony jakofunc map(f, l) = fold((xs, x) => append(xs, f(x)), [], l)
tutaj, „pojedyncza wartość” obliczona przezfold
to tak naprawdę lista.fold
. I nie musi to być lista, wystarczy każda kolekcja, która może być wyrażona jako pusta / niepusta. Co w zasadzie oznacza, że zrobi to dowolny iterator. (wydaje mi się, że wrzucenie słowa „katamorfizm” byłoby zbyt wiele do wprowadzenia dla początkującego :-D)Trudno znaleźć dobre problemy, których nie można rozwiązać za pomocą wbudowanej funkcjonalności. A jeśli jest wbudowany, powinien być używany jako przykład dobrego stylu w języku x.
Na przykład w haskell masz już funkcję
(^)
Preludium.Lub jeśli chcesz to zrobić bardziej programowo
product (replicate y x)
Mówię o tym, że trudno jest pokazać mocne strony stylu / języka, jeśli nie korzystasz z jego funkcji. Jednak może to być dobry krok w kierunku pokazania, jak to działa za kulisami, ale myślę, że powinieneś napisać najlepszy sposób w jakimkolwiek języku, którego używasz, a następnie pomóc osobie stamtąd zrozumieć, co się dzieje w razie potrzeby.
źródło
product
jest to po prostu funkcja skrótufold
z mnożeniem jako jej funkcją i 1 jako początkowym argumentem, ireplicate
jest to funkcja, która generuje iterator (lub listę; jak zauważyłem powyżej dwa są zasadniczo nie do odróżnienia w haskell), co daje daną liczbę identycznych wyników. Teraz powinno być łatwo zrozumieć, jak ta implementacja robi to samo, co odpowiedź @ Jacka powyżej, po prostu używając predefiniowanych specjalnych wersji tych samych funkcji, aby uczynić ją bardziej zwięzłą.