¿Cómo se pueden implementar varargs? Necesitamos algún mecanismo para señalar el final de la lista de argumentos. Esto puede ser
- un valor de terminación especial, o
- la longitud de la lista vararg pasada como un parámetro adicional.
Ambos de estos mecanismos se pueden usar en el contexto del curry para implementar varargs, pero la tipificación adecuada se convierte en un problema importante. Supongamos que estamos tratando con una función sum: ...int -> int
, excepto que esta función usa currying (por lo que en realidad tenemos un tipo más parecido a sum: int -> ... -> int -> int
, excepto que no conocemos el número de argumentos).
Caso: valor de terminador: Deje que end
sea el terminador especial, y T
sea el tipo de sum
. Ahora sabemos que aplicando a end
la función devuelve: sum: end -> int
, y que aplicada a un int obtenemos otra función de suma: sum: int -> T
. Por lo tanto, T
es la unión de estos tipos: T = (end -> int) | (int -> T)
. Al sustituir T
, obtenemos varios tipos posibles, como end -> int
, int -> end -> int
, int -> int -> end -> int
, etc. Sin embargo, la mayoría de los sistemas de tipos no admiten este tipo de tipos.
Caso: longitud explícita: el primer argumento de una función vararg es el número de varargs. Por lo tanto, sum 0 : int
, sum 1 : int -> int
, sum 3 : int -> int -> int -> int
, etc. Esto es compatible con algunos sistemas de tipo y es un ejemplo de tipificación dependiente . En realidad, el número de argumentos sería un parámetro de tipo y no un parámetro regular. No tendría sentido que la aridad de la función dependiera de un valor de tiempo de ejecución, s = ((sum (floor (rand 3))) 1) 2
obviamente no está bien tipeado: esto se evalúa como s = ((sum 0) 1) 2 = (0 1) 2
, s = ((sum 1) 1) 2 = 1 2
o s = ((sum 2) 1) 2 = 3
.
En la práctica, ninguna de estas técnicas debe usarse, ya que son propensas a errores y no tienen un tipo (significativo) en los sistemas de tipos comunes. En su lugar, simplemente pase una lista de valores como un parámetro: sum: [int] -> int
.
Sí, es posible que un objeto aparezca como una función y un valor, por ejemplo. En un sistema de tipo con coerciones. Deje que sum
sea SumObj
, que tiene dos coerciones:
-
coerce: SumObj -> int -> SumObj
permite que sum
se use como una función, y
-
coerce: SumObj -> int
nos permite extraer el resultado.
Técnicamente, esta es una variación del valor del terminador en el caso anterior, con T = SumObj
y coerce
como un-envoltorio para el tipo. En muchos lenguajes orientados a objetos, esto es trivialmente implementable con sobrecarga de operadores, por ejemplo. 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';
}