Estoy pensando en hacer que las funciones curry y variadic estén disponibles en un lenguaje de programación funcional de tipo dinámico, pero me pregunto si es posible o no.
Aquí hay algunos pseudocódigo:
sum = if @args.empty then 0 else @args.head + sum @args.tail
que supuestamente suma todos sus argumentos. Entonces, si sum
se trata a sí mismo un número, entonces el resultado es 0
. por ejemplo,
sum + 1
es igual a 1, asumiendo que +
solo puede funcionar en números. Sin embargo, incluso sum == 0
es cierto, sum
seguirá manteniendo su valor y propiedad funcional sin importar cuántos argumentos se den (por lo tanto, "parcialmente aplicado" y "variadic" al mismo tiempo), por ejemplo, si declaro
g = sum 1 2 3
entonces g
es igual a 6
, sin embargo, aún podemos aplicar más g
. Por ejemplo, g 4 5 == 15
es cierto. En este caso, no podemos reemplazar el objeto g
por un literal 6
, porque aunque producen el mismo valor cuando se tratan como un entero, contienen diferentes códigos en su interior.
Si este diseño se usa en un lenguaje de programación real, ¿causará alguna confusión o ambigüedad?
fuente
sum
tiene0
argumento y recursivamente se llama a sí mismo con un argumento.reduce
?args
:empty
,head
, ytail
. Esas son todas funciones de lista, lo que sugiere que quizás lo más fácil y más sencillo sería usar una lista donde estarían las cosas variadas. (Entonces, ensum [1, 2, 3]
lugar desum 1 2 3
)Respuestas:
¿Cómo se pueden implementar varargs? Necesitamos algún mecanismo para señalar el final de la lista de argumentos. Esto puede ser
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 parecidosum: int -> ... -> int -> int
, excepto que no sabemos el número de argumentos).Caso: valor del terminador: Sea
end
el terminador especial yT
el tipo desum
. Ahora sabemos que se aplica aend
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ónT
, obtenemos diferentes tipos posibles, tales comoend -> 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 cualquieras = ((sum 0) 1) 2 = (0 1) 2
,s = ((sum 1) 1) 2 = 1 2
os = ((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 aSumObj
, que tiene dos coacciones:coerce: SumObj -> int -> SumObj
permitesum
ser utilizado como una función, ycoerce: SumObj -> int
nos permite extraer el resultado.Técnicamente, esta es una variación del caso del valor del terminador anterior, con
T = SumObj
, ycoerce
es un desempaquetador para el tipo. En muchos lenguajes orientados a objetos, esto es trivialmente implementable con sobrecarga del operador, por ejemplo, C ++:fuente
..., force=False)
para forzar la aplicación de la función inicial.curryList : ([a] -> b) -> [a] -> [a] -> b, curryList f xs ys = f (xs ++ ys)
.Es posible que desee ver esta implementación de printf en Haskell , junto con esta descripción de cómo funciona . Hay un enlace en la última página al documento de Oleg Kiselyov sobre hacer este tipo de cosas, que también vale la pena leer. De hecho, si está diseñando un lenguaje funcional, el sitio web de Oleg probablemente debería ser de lectura obligatoria.
En mi opinión, estos enfoques son un poco piratas, pero muestran que es posible. Sin embargo, si su idioma presenta una escritura totalmente dependiente, es mucho más simple. Una función variada para sumar sus argumentos enteros podría verse así:
Una abstracción para definir el tipo recursivo sin necesidad de darle un nombre explícito podría facilitar la escritura de tales funciones.
Editar: por supuesto, acabo de leer la pregunta nuevamente y dijiste un lenguaje escrito dinámicamente , en ese punto obviamente la mecánica de tipo no es realmente relevante, y por lo tanto la respuesta de @ amon probablemente contiene todo lo que necesitas. Oh, bueno, dejaré esto aquí en caso de que alguien se encuentre con esto mientras se pregunta cómo hacerlo en un lenguaje estático ...
fuente
Aquí hay una versión para curry de funciones variadas en Python3 que utiliza el enfoque "terminador" de @amon, aprovechando los argumentos opcionales de Python:
La función devuelta
f
recopila argumentos pasados en llamadas sucesivas en una matriz que está vinculada en el ámbito externo. Solo cuando elforce
argumento es verdadero, se llama a la función original con todos los argumentos recopilados hasta ahora.Las advertencias de esta implementación son que siempre debe pasar un primer argumento para
f
que no pueda crear un "thunk", una función donde todos los argumentos están vinculados y solo se puede invocar con la lista de argumentos vacía (pero creo que esto está en línea con La implementación típica del curry).Otra advertencia es que una vez que pasa un argumento incorrecto (por ejemplo, del tipo incorrecto), debe volver a curry la función original. No hay otra forma de restablecer la matriz interna, esto solo se hace después de una ejecución exitosa de la función currificada.
No sé si su pregunta simplificada, "¿puede un objeto ser una función y un valor no funcional al mismo tiempo?", Puede implementarse en Python, ya que una referencia a una función sin paréntesis se evalúa como el objeto interno de la función . No sé si esto puede doblarse para devolver un valor arbitrario.
Probablemente sería fácil en Lisp, ya que los símbolos de Lisp pueden tener un valor y un valor de función al mismo tiempo; el valor de la función simplemente se selecciona cuando el símbolo aparece en la posición de la función (como el primer elemento de una lista).
fuente