Come possono essere implementati i vararg? Abbiamo bisogno di un meccanismo per segnalare la fine dell'elenco degli argomenti. Questo può essere
- un valore terminatore speciale o
- la lunghezza della lista vararg è passata come parametro extra.
Entrambi questi meccanismi possono essere utilizzati nel contesto del currying per implementare vararg, ma una corretta tipizzazione diventa un problema importante. Supponiamo di avere a che fare con una funzione sum: ...int -> int , tranne per il fatto che questa funzione utilizza il curring (quindi in realtà abbiamo un tipo più simile a sum: int -> ... -> int -> int , tranne per il fatto che non conosciamo il numero di argomenti).
Caso: valore terminatore: lascia che end sia il terminatore speciale e T sia il tipo di sum . Ora sappiamo che applicato a end la funzione restituisce: sum: end -> int , e che applicato a un int otteniamo un'altra funzione di somma: sum: int -> T . Pertanto T è l'unione di questi tipi: T = (end -> int) | (int -> T) . Sostituendo T , otteniamo vari tipi possibili come end -> int , int -> end -> int , int -> int -> end -> int , ecc. Tuttavia, la maggior parte dei sistemi di tipi non accetta tali tipi.
Caso: lunghezza esplicita: il primo argomento di una funzione vararg è il numero di vararg. Quindi sum 0 : int , sum 1 : int -> int , sum 3 : int -> int -> int -> int ecc. Questo è supportato in alcuni sistemi di tipi ed è un esempio di tipizzazione dipendente . In realtà, il numero di argomenti sarebbe un parametro di tipo e non un parametro regolare - non avrebbe senso che l'arità della funzione dipendesse da un valore di runtime, s = ((sum (floor (rand 3))) 1) 2 è ovviamente mal digitato: questo vale sia per s = ((sum 0) 1) 2 = (0 1) 2 , s = ((sum 1) 1) 2 = 1 2 o s = ((sum 2) 1) 2 = 3 .
In pratica, nessuna di queste tecniche dovrebbe essere utilizzata poiché è soggetta a errori e non ha un tipo (significativo) nei sistemi di tipi comuni. Invece, basta passare un elenco di valori come un parametro: sum: [int] -> int .
Sì, è possibile che un oggetto appaia sia come funzione che come valore, ad es. in un sistema di tipo con le coercizioni. Lascia che sum sia un SumObj , che ha due coercizioni:
-
coerce: SumObj -> int -> SumObj consente di utilizzare sum come funzione e
-
coerce: SumObj -> int ci consente di estrarre il risultato.
Tecnicamente, questa è una variazione del caso del valore terminatore sopra, con T = SumObj e coerce che è un non wrapper per il tipo. In molti linguaggi orientati agli oggetti, questo è banalmente implementabile con sovraccarico dell'operatore, ad es. 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';
}