È possibile avere la funzione curry e variadic allo stesso tempo?


13

Sto pensando di rendere disponibili le funzioni curry e variadiche in un linguaggio di programmazione funzionale tipicamente dinamicamente, ma mi chiedo se sia possibile o meno.

Ecco alcuni pseudocodici:

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

che dovrebbe presumibilmente sommare tutti i suoi argomenti. Quindi, se sumstesso viene trattato un numero, il risultato è 0. per esempio,

sum + 1

è uguale a 1, supponendo che +possa funzionare solo sui numeri. Tuttavia, anche sum == 0è vero, summanterrà comunque il suo valore e la sua proprietà funzionale, indipendentemente da quanti argomenti vengono forniti (quindi "parzialmente applicati" e "variadici" allo stesso tempo), ad esempio, se dichiaro

g = sum 1 2 3

allora gè uguale a 6, tuttavia, possiamo ancora applicare ulteriormente g. Ad esempio, g 4 5 == 15è vero. In questo caso, non possiamo sostituire l'oggetto gcon un valore letterale 6, perché sebbene producano lo stesso valore se trattati come numeri interi, contengono codici diversi all'interno.

Se questo design viene utilizzato in un vero linguaggio di programmazione, causerà confusione o ambiguità?


1
A rigor di termini, l'uso del curry come fondamento di una lingua significa che tutte le funzioni sono unarie - non solo non ci sono funzioni variadiche, non ce ne sono nemmeno binarie! Tuttavia, i programmi in quella lingua sembreranno comunque come se avessero preso più argomenti, e questo vale per le funzioni variadiche tanto quanto per quelle normali.
Kilian Foth,

Quindi la mia domanda è semplificata per "un oggetto può essere contemporaneamente una funzione e un valore non funzionale?" Nell'esempio sopra, sumè 0senza argomento e si chiama ricorsivamente con un argomento.
Michael Tsang,

non è questo il lavoro di reduce?
Cricchetto strano

1
Date un'occhiata alle funzioni che si sta utilizzando in args: empty, head, e tail. Queste sono tutte funzioni di elenco, suggerendo che forse la cosa più semplice e più semplice da fare sarebbe usare un elenco in cui si troverebbero le cose variabili. (Quindi, sum [1, 2, 3]invece di sum 1 2 3)
Michael Shaw,

Risposte:


6

Come possono essere implementati varargs? Abbiamo bisogno di un meccanismo per segnalare la fine dell'elenco degli argomenti. Questo può essere

  • un valore di terminazione speciale, o
  • la lunghezza dell'elenco vararg è passata come parametro aggiuntivo.

Entrambi questi meccanismi possono essere utilizzati nel contesto del curry per implementare varargs, ma una corretta digitazione diventa un grosso problema. Supponiamo che abbiamo a che fare con una funzione sum: ...int -> int, tranne per il fatto che questa funzione utilizza il curry (quindi in realtà abbiamo un tipo più simile sum: int -> ... -> int -> int, tranne per il fatto che non conosciamo il numero di argomenti).

Caso: valore del terminatore: endsia il terminatore speciale e Tsia il tipo di sum. Ora sappiamo che applicato endalla funzione restituisce: sum: end -> int, e quella applicata ad un int otteniamo un'altra somma come la funzione: sum: int -> T. Pertanto Tè l'unione di questi tipi: T = (end -> int) | (int -> T). Sostituendo T, otteniamo i vari tipi possibili, come end -> int, int -> end -> int, int -> int -> end -> int, ecc, tuttavia, la maggior parte dei sistemi di tipo non accogliere tali tipi.

Caso: lunghezza esplicita: il primo argomento di una funzione vararg è il numero di varargs. Quindi sum 0 : int, sum 1 : int -> int, sum 3 : int -> int -> int -> intecc Questo è supportata in alcuni sistemi di tipo ed è un esempio di battitura dipendente . In realtà, il numero di argomenti sarebbe un parametro di tipo e non un parametro regolare - non avrebbe senso per l'arietà della funzione di dipendere da un valore di runtime, s = ((sum (floor (rand 3))) 1) 2è, ovviamente, mal digitato: questo restituisce sia s = ((sum 0) 1) 2 = (0 1) 2, s = ((sum 1) 1) 2 = 1 2o s = ((sum 2) 1) 2 = 3.

In pratica, nessuna di queste tecniche dovrebbe essere utilizzata poiché sono soggette a errori e non hanno un tipo (significativo) nei sistemi di tipi comuni. Invece, basta passare un elenco di valori come una paramter: sum: [int] -> int.

Sì, è possibile che un oggetto appaia sia come funzione sia come valore, ad es. In un sistema di tipi con coercizioni. Sia suma SumObj, che ha due coercizioni:

  • coerce: SumObj -> int -> SumObjpermette sumdi essere usato come una funzione, e
  • coerce: SumObj -> int ci consente di estrarre il risultato.

Tecnicamente, questa è una variazione del caso del valore di terminazione sopra, con T = SumObj, ed coerceessendo un involucro per il tipo. In molti linguaggi orientati agli oggetti, questo è banalmente implementabile con sovraccarico dell'operatore, ad esempio 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';
}

Risposta fantastica! Lo svantaggio con l'imballaggio varargs in un elenco è che si perde l'applicazione parziale del curry. Stavo giocando con una versione Python del tuo approccio terminator, usando un argomento di parole chiave ..., force=False)per forzare l'applicazione della funzione iniziale.
ThomasH,

È possibile creare la propria funzione di ordine superiore che applica parzialmente una funzione che accetta un elenco, ad esempio curryList : ([a] -> b) -> [a] -> [a] -> b, curryList f xs ys = f (xs ++ ys).
Jack

2

Potresti voler esaminare questa implementazione di printf in Haskell , insieme a questa descrizione di come funziona . C'è un link in quest'ultima pagina al documento di Oleg Kiselyov su come fare questo genere di cose, che vale anche la pena leggere. In effetti, se stai progettando un linguaggio funzionale, il sito Web di Oleg dovrebbe probabilmente essere una lettura obbligatoria.

Secondo me, questi approcci sono un po 'un trucco, ma dimostrano che è possibile. Se la tua lingua presenta una digitazione completamente dipendente, tuttavia, è molto più semplice. Una funzione variadica per sommare i suoi argomenti interi potrebbe quindi assomigliare a questa:

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

Un'astrazione per la definizione del tipo ricorsivo senza la necessità di dargli un nome esplicito potrebbe facilitare la scrittura di tali funzioni.

Modifica: ovviamente, ho appena letto di nuovo la domanda e tu hai detto un linguaggio tipizzato in modo dinamico , a quel punto ovviamente la meccanica dei tipi non è davvero pertinente, e quindi la risposta di @amon probabilmente contiene tutto ciò di cui hai bisogno. Vabbè, lo lascerò qui nel caso qualcuno lo incontri mentre si chiede come farlo in un linguaggio statico ...


0

Ecco una versione per il curriculum delle funzioni variadiche in Python3 che utilizza l'approccio "terminator" di @amon, sfruttando gli argomenti opzionali di Python:

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

La funzione restituita fraccoglie gli argomenti passati ad essa in successive chiamate in un array associato nell'ambito esterno. Solo quando l' forceargomento è vero, la funzione originale viene chiamata con tutti gli argomenti raccolti finora.

Le avvertenze di questa implementazione sono che devi sempre passare un primo argomento a fquindi non puoi creare un "thunk", una funzione in cui tutti gli argomenti sono associati e possono essere chiamati solo con un elenco di argomenti vuoto (ma penso che sia in linea con l'implementazione tipica del curry).

Un altro avvertimento è che una volta superato un argomento errato (ad es. Di tipo errato) è necessario ripetere il curriculum della funzione originale. Non esiste altro modo per ripristinare l'array interno, ciò avviene solo dopo una corretta esecuzione della funzione curry.

Non so se la tua domanda semplificata, "un oggetto può essere una funzione e un valore non funzionale allo stesso tempo?", Può essere implementata in Python, poiché un riferimento a una funzione senza parentesi si riferisce all'oggetto funzione interno . Non so se questo può essere piegato per restituire un valore arbitrario.

Probabilmente sarebbe facile in Lisp, poiché i simboli Lisp possono avere contemporaneamente un valore e un valore di funzione; il valore della funzione viene semplicemente selezionato quando il simbolo appare nella posizione della funzione (come primo elemento in un elenco).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.