Czy groovy nazywa częściowe stosowanie „curry”?

15

Groovy ma pojęcie, które nazywa „curry”. Oto przykład z ich wiki:

def divide = { a, b -> a / b }

def halver = divide.rcurry(2)

assert halver(8) == 4

Rozumiem, co się tutaj dzieje, że prawy argument dividezwiązany jest z wartością 2. To wydaje się być formą częściowego zastosowania.

Termin curry jest zwykle używany w celu przekształcenia funkcji, która pobiera szereg argumentów w funkcję, która przyjmuje tylko jeden argument i zwraca inną funkcję. Na przykład tutaj jest typ curryfunkcji w Haskell:

curry :: ((a, b) -> c) -> (a -> (b -> c))

Dla ludzi, którzy nie korzystali Haskell a, bi csą wszystkie parametry rodzajowe. curryprzyjmuje funkcję z dwoma argumentami i zwraca funkcję, która pobiera ai zwraca funkcję od bdo c. Dodałem dodatkową parę nawiasów do tego typu, aby to wyjaśnić.

Czy źle zrozumiałem, co dzieje się w tym świetnym przykładzie, czy jest to jedynie błędna nazwa częściowego zastosowania? Czy robi to w rzeczywistości obie funkcje: to znaczy przekształca się dividew funkcję curry, a następnie częściowo stosuje się 2do tej nowej funkcji.

Richard Warburton
źródło
1
dla tych, którzy nie mówią, haskell msmvps.com/blogs/jon_skeet/archive/2012/01/30/…
jk.

Odpowiedzi:

14

Implementacja curryGroovy'ego nie curry w żadnym momencie, nawet za kulisami. Jest zasadniczo identyczny z częściowym zastosowaniem.

Te curry, rcurryoraz ncurrymetody zwracają CurriedClosureobiekt , który przechowuje związanego argumenty. Ma także metodę getUncurriedArguments(źle nazwaną - curry funkcje, a nie argumenty), która zwraca skład argumentów przekazanych mu wraz z powiązanymi argumentami.

Kiedy zamknięcie jest wywoływana, to ostatecznie nazywa się invokeMethodmetodęMetaClassImpl , która jawnie sprawdza, czy obiekt wywołujący jest instancją CurriedClosure. Jeśli tak, wykorzystuje powyższe getUncurriedArgumentsdo ułożenia pełnej tablicy argumentów do zastosowania:

if (objectClass == CurriedClosure.class) {
    // ...
    final Object[] curriedArguments = cc.getUncurriedArguments(arguments);
    // [Ed: Yes, you read that right, curried = uncurried. :) ]
    // ...
    return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments);
}

Opierając się na mylącym i nieco niekonsekwentnym nazewnictwie powyżej, podejrzewam, że ktokolwiek to napisał, ma dobre zrozumienie pojęciowe, ale być może był nieco pochopny i - jak wielu inteligentnych ludzi - splątał curry z częściowym zastosowaniem. Jest to zrozumiałe (patrz odpowiedź Paula Kinga), jeśli trochę niefortunne; trudno będzie to naprawić bez naruszenia wstecznej kompatybilności.

Jednym z rozwiązań, które zasugerowałem, jest przeciążenie currymetody w taki sposób, że gdy nie zostaną przekazane żadne argumenty, powoduje ona prawdziwe curry i przestaje być wywoływaniem metody z argumentami na korzyść nowej partialfunkcji. Może to wydawać się trochę dziwne , ale maksymalizuje zgodność wsteczną - ponieważ nie ma powodu, aby używać częściowej aplikacji z zerowymi argumentami - unikając przy tym brzydszej sytuacji (IMHO) posiadania nowej, różnie nazwanej funkcji do właściwego curry, podczas gdy funkcja faktycznie nazwany curryrobi coś innego i myląco podobnego.

Jest curryrzeczą oczywistą , że wynik wywołania jest zupełnie inny niż faktyczne curry. Jeśli funkcja naprawdę curry tę funkcję, możesz napisać:

def add = { x, y -> x + y }
def addCurried = add.curry()   // should work like { x -> { y -> x + y } }
def add1 = addCurried(1)       // should work like { y -> 1 + y }
assert add1(1) == 2 

… I to by działało, bo addCurriedpowinno działać jak { x -> { y -> x + y } }. Zamiast tego generuje wyjątek czasu wykonywania i umierasz trochę w środku.

Jordan Gray
źródło
1
Myślę, że rcurry i ncurry na funkcjach z argumentami> 2 pokazują, że tak naprawdę jest to tylko częściowa aplikacja, a nie curry
jk.
@jk W rzeczywistości można to wykazać na funkcjach z argumentami == 2, jak zauważam na końcu. :)
Jordan Gray
3
@matcauthon Ściśle mówiąc, „celem” curry jest przekształcenie funkcji z wieloma argumentami w zagnieżdżony łańcuch funkcji z jednym argumentem. Myślę, że to, o co prosisz, to praktyczny powód, dla którego chcesz użyć curry, co jest nieco trudniejsze do uzasadnienia w Groovy niż np. W LISP lub Haskell. Chodzi o to, że prawdopodobnie chcesz przez większość czasu korzystać z częściowej aplikacji, a nie curry.
Jordan Gray
4
+1 zaand you die a little inside
Thomas Eding
1
Wow, lepiej rozumiem curry po przeczytaniu twojej odpowiedzi: +1 i dziękuję! Specyficzne dla pytania, podoba mi się to, że powiedziałeś: „mieszanie curry z częściowym zastosowaniem”.
GlenPeterson
3

Myślę, że jest jasne, że groovy curry jest w rzeczywistości częściowym zastosowaniem, gdy rozważa się funkcje z więcej niż dwoma argumentami. rozważać

f :: (a,b,c) -> d

jego curry będzie

fcurried :: a -> b -> c -> d

jednak curry groovy zwróci coś równoważnego (zakładając, że wywoływany z 1 argumentem x)

fgroovy :: (b,c) -> d 

która wywoła f o wartości stałej x

tzn. chociaż curry Groovy może zwracać funkcje z argumentami N-1, funkcje curry poprawnie mają tylko 1 argument, dlatego groovy nie może curry z curry

jk.
źródło
2

Groovy zapożyczył nazwy swoich metod curry od wielu innych nieczystych języków FP, które również używają podobnego nazewnictwa do częściowego zastosowania - być może niefortunne dla takiej funkcji skoncentrowanej na FP. Proponuje się kilka „prawdziwych” implementacji curry do włączenia do Groovy. Dobry wątek, aby zacząć o nich czytać, znajduje się tutaj:

http://groovy.markmail.org/thread/c4ycxdzm3ack6xxb

Istniejąca funkcjonalność pozostanie w pewnej formie, a zgodność z poprzednimi wersjami będzie brana pod uwagę podczas wywoływania, jak nazwać nowe metody itp. - dlatego nie mogę powiedzieć na tym etapie, jakie będzie ostateczne nazewnictwo nowych / starych metod być. Prawdopodobnie kompromis w nazewnictwie, ale zobaczymy.

Dla większości programistów OO rozróżnienie między tymi dwoma terminami (curry i częściowe zastosowanie) jest prawdopodobnie w dużej mierze akademickie; jednak gdy już się do nich przyzwyczaisz (i kto będzie utrzymywał twój kod, zostanie przeszkolony w zakresie czytania tego stylu kodowania), wówczas programowanie w stylu bez punktów lub milczenie (które obsługuje „prawdziwe” curry) pozwala na bardziej zwięzłe wyrażanie pewnych algorytmów a w niektórych przypadkach bardziej elegancko. Oczywiste jest tutaj pewne „piękno leży w oczach patrzącego”, ale możliwość obsługi obu stylów jest zgodna z naturą Groovy'ego (OO / FP, static / dynamic, class / scripts itp.).

Paul King
źródło
1

Biorąc pod uwagę tę definicję znalezioną w IBM:

Termin curry pochodzi od matematyka Haskella Curry'ego, który opracował pojęcie funkcji cząstkowych. Curry odnosi się do łączenia wielu argumentów w funkcję, która pobiera wiele argumentów, w wyniku czego nowa funkcja przyjmuje pozostałe argumenty i zwraca wynik.

halverto twoja nowa (curry) funkcja (lub zamknięcie), która teraz przyjmuje tylko jeden parametr. Dzwonienie halver(10)spowoduje 5.

Dlatego przekształca funkcję z n argumentami w funkcję z n-1 argumentami. To samo mówi przykład Haskell, co robi curry.

matcauthon
źródło
4
Definicja IBM jest niepoprawna. To, co określają jako curry, jest w rzeczywistości aplikacją funkcji częściowej, która wiąże (naprawia) argumenty funkcji, aby utworzyć funkcję o mniejszej aryczności. Curry przekształca funkcję, która przyjmuje wiele argumentów w łańcuch funkcji, z których każda przyjmuje jeden argument.
Jordan Gray
1
W definicji wikipedia jest taka sama jak w IBM: W matematyce i informatyce curry jest techniką przekształcania funkcji, która przyjmuje wiele argumentów (lub n-krotkę argumentów) w taki sposób, że można ją nazwać łańcuch funkcji, każdy z jednym argumentem (częściowa aplikacja). Groovy przekształca funkcję (z dwoma argumentami) za pomocą rcurryfunkcji (która przyjmuje jeden argument) w funkcję (z teraz tylko jednym argumentem). Połączyłem funkcję curry z argumentem mojej funkcji podstawowej, aby uzyskać wynikową funkcję.
matcauthon
3
nie, definicja wikipedii jest inna - częściowe zastosowanie ma miejsce, gdy wywołujesz funkcję curry - a nie, gdy definiujesz ją, co robi groovy
jk.
6
@jk jest poprawne. Przeczytaj ponownie wyjaśnienie Wikipedii, a zobaczysz, że zwracany jest łańcuch funkcji z jednym argumentem, a nie jedną funkcją z n - 1argumentami. Zobacz przykład na końcu mojej odpowiedzi; zobacz także w dalszej części artykułu, aby uzyskać więcej informacji na temat wprowadzanego rozróżnienia. en.wikipedia.org/wiki/…
Jordan Gray
4
To bardzo znaczące, zaufaj mi. Ponownie kod na końcu mojej odpowiedzi pokazuje, jak działałoby prawidłowe wdrożenie; z jednej strony nie wymagałoby to żadnych argumentów. Obecna implementacja powinna być naprawdę nazwana np partial.
Jordan Gray