¿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 terminador especial, o
- La longitud de la lista vararg pasada como parámetro adicional.
Ambos 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 utiliza curry (por lo que en realidad tenemos un tipo más parecido sum: int -> ... -> int -> int
, excepto que no sabemos el número de argumentos).
Caso: valor del terminador: Sea end
el terminador especial y T
el tipo de sum
. Ahora sabemos que se aplica a end
la función devuelve: sum: end -> int
y que se aplica a un int tenemos otra suma similar a la función: sum: int -> T
. Por lo tanto, T
es la unión de estos tipos: T = (end -> int) | (int -> T)
. Mediante la sustitución T
, obtenemos diferentes tipos posibles, tales como end -> int
, int -> end -> int
, int -> int -> end -> int
, etc. Sin embargo, la mayoría de los sistemas de tipo no dan cabida a esos tipos.
Caso: longitud explícita: el primer argumento para una función vararg es el número de varargs. Así sum 0 : int
, sum 1 : int -> int
, sum 3 : int -> int -> int -> int
etc. Esto se apoya en 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 normal - no tendría sentido que la aridad de la función que depender de un valor de tiempo de ejecución, s = ((sum (floor (rand 3))) 1) 2
es obviamente mal escrito-: Evalúa a cualquiera 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, sólo tiene que pasar una lista de valores como uno 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 tipos con coacciones. Let sum
be a SumObj
, que tiene dos coacciones:
coerce: SumObj -> int -> SumObj
permite sum
ser utilizado como una función, y
coerce: SumObj -> int
nos permite extraer el resultado.
Técnicamente, esta es una variación del caso del valor del terminador anterior, con T = SumObj
, y coerce
es un desempaquetador para el tipo. En muchos lenguajes orientados a objetos, esto es trivialmente implementable con sobrecarga del operador, 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';
}