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';
}