Czy możliwe jest jednoczesne korzystanie z funkcji curry i variadic?

13

Zastanawiam się nad udostępnieniem funkcji curry i variadic w funkcjonalnym języku programowania z dynamicznym pisaniem, ale zastanawiam się, czy jest to możliwe, czy nie.

Oto kilka pseudokodów:

sum = if @args.empty then 0 else @args.head + sum @args.tail

który rzekomo ma podsumować wszystkie argumenty. Następnie, jeśli sumsama traktowana jest liczba, wynik jest następujący 0. na przykład,

sum + 1

jest równe 1, przy założeniu, że +może działać tylko na liczbach. Jednak nawet sum == 0jest prawdą, sumnadal zachowuje swoją wartość i właściwość funkcjonalną bez względu na liczbę podanych argumentów (stąd „częściowo zastosowane” i „variadyczne” w tym samym czasie), na przykład, jeśli zadeklaruję

g = sum 1 2 3

następnie gjest równa 6, jednak nadal możemy dodatkowo zastosować g. Na przykład g 4 5 == 15prawda. W tym przypadku nie możemy zastąpić obiektu gliterałem 6, ponieważ chociaż dają taką samą wartość, gdy są traktowane jako liczby całkowite, zawierają w sobie różne kody.

Jeśli ten projekt zostanie użyty w prawdziwym języku programowania, czy spowoduje jakieś zamieszanie lub dwuznaczność?

Michael Tsang
źródło
1
Ściśle mówiąc, używanie curry jako podstawy języka oznacza, że wszystkie funkcje są jedne - nie tylko nie ma funkcji wariadycznych, nie ma nawet funkcji binarnych! Jednak programy w tym języku będą nadal wyglądały, jakby pobierały wiele argumentów, a to dotyczy funkcji variadic tak samo, jak normalnych.
Kilian Foth,
Następnie moje pytanie jest uproszczone do „czy obiekt może być jednocześnie funkcją i wartością niefunkcjonalną?” W powyższym przykładzie sumjest 0bez argumentu i rekurencyjnie wywołuje się z argumentem.
Michael Tsang,
czy to nie praca reduce?
maniak zapadkowy
1
Spójrz na funkcjach jesteś korzystających na args: empty, head, i tail. Są to wszystkie funkcje listy, co sugeruje, że być może łatwiejszą i prostszą rzeczą byłoby skorzystanie z listy zawierającej różne warianty. (Więc sum [1, 2, 3]zamiast sum 1 2 3)
Michael Shaw,

Odpowiedzi:

6

Jak można wdrożyć varargs? Potrzebujemy mechanizmu sygnalizującego koniec listy argumentów. To może być albo

  • specjalna wartość terminatora lub
  • długość listy vararg przekazana jako dodatkowy parametr.

Oba te mechanizmy mogą być używane w kontekście curry do implementacji varargs, ale prawidłowe pisanie staje się poważnym problemem. Załóżmy, że mamy do czynienia z funkcją sum: ...int -> int, z wyjątkiem tego, że ta funkcja korzysta z curry (więc faktycznie mamy typ bardziej podobny sum: int -> ... -> int -> int, z tym wyjątkiem, że nie znamy liczby argumentów).

Przypadek: wartość terminatora: Niech endbędzie specjalnym terminatorem i Tbędzie typem sum. Teraz wiemy, że stosuje się do endzwrotów funkcyjnych: sum: end -> inti że stosowane do int możemy uzyskać inną sumę podobnego do funkcji: sum: int -> T. Dlatego Tjest sumą tych typów: T = (end -> int) | (int -> T). Podstawiając Totrzymujemy różne możliwe typy, takie jak end -> int, int -> end -> int, int -> int -> end -> int, itd. Jednak większość systemów typu nie pomieścić takie typy.

Przypadek: długość jawna: pierwszym argumentem funkcji vararg jest liczba varargs. Więc sum 0 : int, sum 1 : int -> int, sum 3 : int -> int -> int -> intitd. To jest obsługiwana w niektórych systemach typu i jest przykładem pisania zależnej . Faktycznie, liczba argumentów byłby parametr typu, a nie regularny parametr - to nie ma sensu na liczbę operandów funkcji zależy od wartości wykonawczego, s = ((sum (floor (rand 3))) 1) 2jest oczywiście źle wpisane: ten ma wartość albo s = ((sum 0) 1) 2 = (0 1) 2, s = ((sum 1) 1) 2 = 1 2albo s = ((sum 2) 1) 2 = 3.

W praktyce nie należy stosować żadnej z tych technik, ponieważ są one podatne na błędy i nie mają (znaczącego) typu w typowych systemach. Zamiast tego, po prostu przekazać listę wartości jako jeden paramter: sum: [int] -> int.

Tak, obiekt może pojawiać się zarówno jako funkcja, jak i wartość, np. W systemie pisma z przymusami. Niech sumbędzie a SumObj, który ma dwa przymusu:

  • coerce: SumObj -> int -> SumObjpozwala sumna użycie jako funkcji, oraz
  • coerce: SumObj -> int pozwala nam wyodrębnić wynik.

Technicznie rzecz biorąc, jest to wariant powyższego przypadku wartości terminatora T = SumObj, coercektóry jest rozpakowaniem dla tego typu. W wielu językach obiektowych jest to trywialnie możliwe do wykonania przy przeciążeniu operatora, np. C ++:

#include <iostream>
using namespace std;

class sum {
  int value;
public:
  explicit sum() : sum(0) {}
  explicit sum(int x) : value(x) {}
  sum operator()(int x) const { return sum(value + x); }  // function call overload
  operator int() const { return value; } // integer cast overload
};

int main() {
  int zero = sum();
  cout << "zero sum as int: " << zero << '\n';
  int someSum = sum(1)(2)(4);
  cout << "some sum as int: " << someSum << '\n';
}
amon
źródło
Świetna odpowiedź! Wadą pakowania varargs na liście jest to, że tracisz częściowe zastosowanie curry. Bawiłem się wersją twojego terminatora w Pythonie, używając argumentu słowa kluczowego, ..., force=False)aby wymusić zastosowanie funkcji początkowej.
ThomasH
Możesz stworzyć własną funkcję wyższego rzędu, która częściowo zastosuje funkcję pobierającą listę, na przykład curryList : ([a] -> b) -> [a] -> [a] -> b, curryList f xs ys = f (xs ++ ys).
Jack
2

Możesz spojrzeć na tę implementację printf w Haskell , wraz z opisem tego, jak to działa . Na drugiej stronie znajduje się link do artykułu Olega Kiselyova na temat robienia tego rodzaju rzeczy, które również warto przeczytać. W rzeczywistości, jeśli projektujesz funkcjonalny język, strona internetowa Olega powinna być prawdopodobnie obowiązkowa.

Moim zdaniem te podejścia są trochę hack, ale pokazują, że jest to możliwe. Jeśli jednak Twój język umożliwia pisanie w pełni zależne, jest to o wiele prostsze. Funkcja wariadyczna sumująca argumenty liczb całkowitych mogłaby wyglądać mniej więcej tak:

type SumType = (t : union{Int,Null}) -> {SumType, if t is Int|
                                         Int,     if t is Null}
sum :: SumType
sum (v : Int) = v + sum
sum (v : Null) = 0

Abstrakcja do zdefiniowania typu rekurencyjnego bez konieczności nadawania mu wyraźnej nazwy może ułatwić pisanie takich funkcji.

Edycja: oczywiście, po prostu ponownie przeczytałem pytanie i powiedziałeś dynamicznie pisany język, w którym to momencie oczywiście mechanika typów nie jest tak naprawdę istotna, dlatego odpowiedź @ amon prawdopodobnie zawiera wszystko, czego potrzebujesz. No cóż, zostawię to tutaj na wypadek, gdyby ktoś się z tym spotkał, zastanawiając się, jak to zrobić w języku statycznym ...

Jules
źródło
0

Oto wersja do curryowania różnych funkcji w Pythonie, która wykorzystuje podejście „terminatora” @amona, wykorzystując opcjonalne argumenty Pythona:

def curry_vargs(g):
    actual_args = []
    def f(a, force=False):
        nonlocal actual_args
        actual_args.append(a)
        if force:
            res = g(*actual_args)
            actual_args = []
            return res
        else:
            return f
    return f

def g(*args): return sum(args)
f = curry_vargs(g)
f(1)(2)(3)(4,True) # => 10

Zwracana funkcja fzbiera argumenty przekazywane jej w kolejnych wywołaniach w tablicy powiązanej z zewnętrznym zasięgiem. Tylko wtedy, gdy forceargument jest prawdziwy, wywoływana jest funkcja oryginalna ze wszystkimi zebranymi do tej pory argumentami.

Zastrzeżenia do tej implementacji polegają na tym, że zawsze musisz przekazać pierwszy argument, aby fnie można było utworzyć „thunk”, funkcji, w której wszystkie argumenty są powiązane i można je wywoływać tylko z pustą listą argumentów (ale myślę, że jest to zgodne z typowa realizacja curry).

Kolejnym zastrzeżeniem jest to, że po podaniu niewłaściwego argumentu (np. Niewłaściwego typu) należy ponownie zakryć pierwotną funkcję. Nie ma innego sposobu na zresetowanie tablicy wewnętrznej, odbywa się to tylko po pomyślnym wykonaniu funkcji curry.

Nie wiem, czy twoje uproszczone pytanie „czy obiekt może być jednocześnie funkcją i wartością niefunkcjonalną?”, Może zostać zaimplementowane w Pythonie, ponieważ odwołanie do funkcji bez nawiasów odnosi się do wewnętrznego obiektu funkcji . Nie wiem, czy można to wygiąć, aby zwrócić dowolną wartość.

Prawdopodobnie byłoby to łatwe w Lisp, ponieważ symbole Lisp mogą mieć jednocześnie wartość i wartość funkcji; wartość funkcji wybiera się po prostu, gdy symbol pojawia się w pozycji funkcji (jako pierwszy element na liście).

ThomasH
źródło