Codziennie czytam artykuły na temat programowania funkcjonalnego i staram się stosować jak najwięcej praktyk. Ale nie rozumiem, co jest wyjątkowego w curry lub częściowym stosowaniu.
Weź ten kod Groovy jako przykład:
def mul = { a, b -> a * b }
def tripler1 = mul.curry(3)
def tripler2 = { mul(3, it) }
Nie rozumiem, jaka jest różnica między tripler1
i tripler2
. Czy oba nie są takie same? „Curry” jest obsługiwane w czystych lub częściowych językach funkcjonalnych, takich jak Groovy, Scala, Haskell itp. Ale mogę zrobić to samo (lewe curry, prawe curry, n-curry lub częściowe zastosowanie), po prostu tworząc inny nazwany lub anonimowy funkcja lub zamknięcie, które przekaże parametry do pierwotnej funkcji (jak tripler2
) w większości języków (nawet C.)
Czy coś mi umyka? Są miejsca, w których mogę używać curry i częściowej aplikacji w mojej aplikacji Grails, ale waham się, aby to zrobić, ponieważ zadaję sobie pytanie „Jak to inaczej?”
Proszę, oświeć mnie.
EDYCJA: Czy mówicie, że częściowa aplikacja / curry jest po prostu bardziej wydajna niż tworzenie / wywoływanie innej funkcji, która przekazuje domyślne parametry do pierwotnej funkcji?
źródło
f x y = x + y
oznacza, żef
jest to funkcja, która przyjmuje jeden parametr int. Wynikf x
(f
zastosowany dox
) jest funkcją, która przyjmuje jeden parametr int. Wynikf x y
(lub(f x) y
np.f x
Zastosowanyy
) jest wyrażeniem, które nie przyjmuje parametrów wejściowych i jest oceniane przez redukcjęx + y
.Odpowiedzi:
Curry polega na zamianie / reprezentowaniu funkcji, która pobiera n danych wejściowych do n funkcji, z których każda przyjmuje 1 sygnał wejściowy. Częściowe zastosowanie polega na ustaleniu niektórych danych wejściowych funkcji.
Motywacja do częściowego zastosowania polega przede wszystkim na tym, że ułatwia pisanie bibliotek funkcji wyższego rzędu. Na przykład wszystkie algorytmy w C ++ STL w większości przyjmują predykaty lub funkcje jednoargumentowe, bind1st pozwala użytkownikowi biblioteki na dołączenie niejednorodnych funkcji ze związaną wartością. Piszący bibliotekę nie musi więc zapewniać przeciążonych funkcji dla wszystkich algorytmów, które przyjmują jednoargumentowe funkcje w celu zapewnienia wersji binarnych
Samo curry jest użyteczne, ponieważ daje ci częściową aplikację w dowolnym miejscu za darmo, tzn. Nie potrzebujesz już funkcji takiej jak
bind1st
częściowe zastosowanie.źródło
currying
coś specyficznego dla groovy lub ma zastosowanie we wszystkich językach?Optymalizator przyjrzy się temu i natychmiast przejdzie do czegoś, co może zrozumieć. Curry to niezła sztuczka dla użytkownika końcowego, ale przynosi znacznie lepsze korzyści z punktu widzenia projektowania języka. To naprawdę miło obsługiwać wszystkie metody jako jednoskładnikowa,
A -> B
gdzieB
może być inna metoda.Upraszcza to, jakie metody musisz pisać, aby obsługiwać funkcje wyższego rzędu. Twoja statyczna analiza i optymalizacja w języku ma tylko jedną ścieżkę do pracy, która zachowuje się w znany sposób. Wiązanie parametrów po prostu wypada z projektu, zamiast wymagać od obręczy wykonywania tego typowego zachowania.
źródło
Jako @jk. Nawiązując do curry, może pomóc uczynić kod bardziej ogólnym.
Załóżmy na przykład, że masz te trzy funkcje (w Haskell):
Funkcja
f
przyjmuje tutaj dwie funkcje jako argumenty, przekazuje1
do pierwszej funkcji i przekazuje wynik pierwszego wywołania do drugiej funkcji.Gdybyśmy mieli zadzwonić
f
za pomocąq
ir
jako argumentów, efektywnie by to robił:gdzie
q
zostanie zastosowane1
i zwróci inną funkcję (jakq
curry); ta zwrócona funkcja byłaby następnie przekazanar
jako argument do podania argumentu3
. Wynikiem tego będzie wartość9
.Powiedzmy, że mieliśmy dwie inne funkcje:
moglibyśmy je
f
również przekazać i uzyskać wartość7
lub15
, w zależności od tego, czy nasze argumenty byłys t
lubt s
. Ponieważ obie te funkcje zwracają wartość, a nie funkcję, żadna częściowa aplikacja nie miałaby miejsca wf s t
lubf t s
.Gdybyśmy pisali
f
z myśląq
ir
myślą, moglibyśmy użyć lambda (funkcja anonimowa) zamiast częściowej aplikacji, np .:ale to ograniczyłoby ogólność
f'
.f
można wywoływać za pomocą argumentówq
ir
lubs
it
, alef'
można je wywoływać tylko za pomocąq
ir
-f' s t
if' t s
oba powodują błąd.WIĘCEJ
Gdyby
f'
zostały wywołane za pomocą paryq'
/, wr'
którejq'
wzięto więcej niż dwa argumenty,q'
nadal byłby częściowo zastosowanyf'
.Alternatywnie możesz owinąć się na
q
zewnątrzf
zamiast wewnątrz, ale to dałoby ci paskudną zagnieżdżoną lambdę:co w gruncie rzeczy
q
było curry !źródło
def f = { a, b -> b a.curry(1) }
, abyf q, r
do pracy idef f = { a, b -> b a(1) }
czydef f = { a, b -> b a.curry(1)() }
wf s, t
do pracy. Musisz przekazać wszystkie parametry lub wyraźnie powiedzieć, że curry. :(f x y
oznacza to, ile języków napisałbyf(x)(y)
, a nief(x, y)
. Być może twój kod działałby w Groovy, jeśli piszeszq
tak, że spodziewa się, że zostanie wywołany jakq(1)(2)
?(partial f a b ...)
- będąc przyzwyczajonym do Haskella, bardzo brakuje mi właściwego curry podczas programowania w innych językach (chociaż ostatnio pracuję w F #, który, na szczęście, obsługuje).Istnieją dwa kluczowe punkty dotyczące częściowego zastosowania. Pierwsza to składnia / wygoda - niektóre definicje stają się łatwiejsze i krótsze do czytania i pisania, jak wspomniano w @jk. (Sprawdź programowanie Pointfree, aby dowiedzieć się więcej o tym, jak niesamowite jest to!)
Drugi, jak wspomniano @telastyn, dotyczy modelu funkcji i nie jest jedynie wygodny. W wersji Haskell, z której otrzymam moje przykłady, ponieważ nie znam innych języków z częściową aplikacją, wszystkie funkcje pobierają pojedynczy argument. Tak, nawet takie funkcje jak:
weź jeden argument; z powodu asocjatywności konstruktora typu funkcji
->
powyższe jest równoważne z:która jest funkcją, która pobiera
a
i zwraca funkcję[a] -> [a]
.To pozwala nam pisać funkcje takie jak:
który może zastosować dowolną funkcję do argumentu odpowiedniego typu. Nawet szalone, takie jak:
Okej, to był wymyślony przykład. Ale bardziej przydatna jest klasa typu Applicative , która obejmuje tę metodę:
Jak widać, typ jest identyczny jak w
$
przypadku pobraniaApplicative f
bitu, a w rzeczywistości ta klasa opisuje zastosowanie funkcji w kontekście. Więc zamiast normalnej aplikacji funkcji:Możemy zastosować funkcje w kontekście aplikacyjnym; na przykład w kontekście Może, w którym coś może być obecne lub brakujące:
Teraz naprawdę fajne jest to, że klasa typu Applicative nie wspomina nic o funkcjach więcej niż jednego argumentu - niemniej może sobie z nimi poradzić, nawet funkcje 6 argumentów takich jak
f
:O ile mi wiadomo, klasa typu Wnioskodawca w swojej ogólnej formie nie byłaby możliwa bez pewnej koncepcji częściowego zastosowania. (Do dowolnego programowania ekspertów tam - proszę mnie poprawić, jeśli się mylę!) Oczywiście, jeśli język brakuje częściową aplikacji, mogłaby zbudować go w jakiejś formie, ale ... to nie jest tak samo, to jest ? :)
źródło
Applicative
bez curry lub częściowego zastosowaniafzip :: (f a, f b) -> f (a, b)
. W języku z funkcjami wyższego rzędu pozwala to przenieść curry i częściowe zastosowanie do kontekstu funktora i jest równoważne z(<*>)
. Bez funkcji wyższego rzędu nie będziesz mieć,fmap
więc wszystko byłoby bezużyteczne.f <$> x <*> y
idiomatyczny styl działa łatwo, ponieważ curry i częściowa aplikacja łatwo działają. Innymi słowy, to, co przyjemne, jest ważniejsze niż to, co jest możliwe tutaj.