Articulo de referencia

Combinador de punto fijo

En lógica combinatoria para ciencias de la computación , un combinador de punto fijo (o combinador de punto fijo ) [ 1 ] : p.26 es una función de orden superior (es decir, una f...

En lógica combinatoria para ciencias de la computación , un combinador de punto fijo (o combinador de punto fijo ) [ 1 ] : p.26 es una función de orden superior (es decir, una función que toma una función como argumento ) que devuelve algún punto fijo (un valor que se asigna a sí mismo) de su función argumento, si existe.

Formalmente, siFiincógnita{\displaystyle \mathrm {fix} }es un combinador de punto fijo y la funciónF{\displaystyle f}tiene uno o más puntos fijos, entoncesFiincógnita F{\displaystyle \mathrm {fix} \ f}es uno de estos puntos fijos, es decir,

Fiincógnita F =F (Fiincógnita F).{\displaystyle \mathrm {arreglar} \ f\ =f\ (\mathrm {arreglar} \ f).}

Los combinadores de punto fijo se pueden definir en el cálculo lambda y en los lenguajes de programación funcional , y proporcionan un medio para permitir definiciones recursivas .

Introducción

Aplicado a una función no constante de una variable que trata su argumento como un dato (como por ejemplo la función seno), el combinador Y generalmente no termina. Y está pensado para usarse con codatos, por ejemplo, un constructor de lista de columnas que coloca el primer elemento y espera "el resto de los elementos" como argumento, que se desarrollará más adelante; o una función de orden superior que espera que se le proporcione como primer argumento una función "para calcular el resto", la cual puede llamar o no, según sea necesario.

Aplicado a dicha "función de un solo paso", Y se encarga de crear la función "resto del cálculo", que consiste en el mismo paso que se le dio y el "resto del cálculo" nuevamente, lo que en realidad significa simplemente el paso original repetido tantas veces como sea necesario, llamando a la función "resto" cuando sea necesario, pero no siempre. La función resultante se comporta como un bucle while o for . Utilizado de esta manera, el combinador Y implementa la recursión general .

El cálculo lambda no tiene nombres globales, es decir, no permite que una función se refiera a sí misma por su nombre dentro de su definición, como sí es posible en muchos lenguajes de programación . En cambio, sí tiene nombres locales, es decir, parámetros en las abstracciones lambda. Un término de abstracción puede recibir otro término como argumento y referirse a ese argumento mediante el nombre del parámetro dentro del cuerpo de la función.

El combinador Y también puede utilizarse para implementar la paradoja de Curry . El núcleo de la paradoja de Curry reside en que el cálculo lambda sin tipos es un sistema deductivo deficiente, y el combinador Y lo demuestra al permitir que una expresión anónima represente cero, o incluso muchos valores. Esto resulta inconsistente en la lógica matemática .

Combinador Y en cálculo lambda

En el cálculo lambda clásico sin tipos , cada función tiene un punto fijo. Una implementación particular deFiincógnita{\displaystyle \mathrm {fix} }es el combinador paradójico Y de Haskell Curry , dado por [ 2 ] : 131 [ nota 1 ] [ nota 2 ]

Y=λF. (λincógnita.F (incógnita incógnita)) (λincógnita.F (incógnita incógnita)){\displaystyle \mathrm {Y} =\lambda f.\ (\lambda xf\ (x\ x))\ (\lambda xf\ (x\ x))}

(Aquí se utilizan las notaciones y convenciones estándar del cálculo lambda: Y es una función que toma un argumento f y devuelve la expresión completa que sigue al primer período; la expresiónλincógnita.F (incógnita incógnita){\displaystyle \lambda xf\ (x\ x)}denota una función que toma un argumento x , considerado como una función, y devuelve la expresiónF (incógnita incógnita){\displaystyle f\ (x\ x)}, dónde(incógnita incógnita){\displaystyle (x\ x)}denota x aplicada a sí misma. La yuxtaposición de expresiones denota la aplicación de una función , es asociativa por la izquierda y tiene mayor precedencia que el punto.

Verificación

El siguiente cálculo verifica queYgramo{\displaystyle \mathrm {Y} g}es de hecho un punto fijo de la funcióngramo{\displaystyle g}:

El término lambdagramo (Y gramo){\displaystyle g\ (\mathrm {Y} \ g)}En general, puede que no se reduzca β al términoY gramo{\displaystyle \mathrm {Y} \g}Sin embargo, ambos términos se reducen β al mismo término, como se muestra.

Ejemplos de implementación

A continuación se presenta un ejemplo de implementación de Y en el lenguaje R :

Y <- \ ( f ) { g <- \ ( x ) f ( x ( x )) g ( g ) }

Esto se puede utilizar para implementar el factorial de la siguiente manera:

hecho <- \ ( f ) \ ( n ) si ( n == 0 ) 1 sino n * f ( n - 1 ) Y ( hecho )( 5 ) # produce 5! = 120

La letra Y solo es necesaria cuando no se especifican los nombres de las funciones. Sustituyendo todas las definiciones en una sola línea, de modo que no se requieran los nombres de las funciones, se obtiene:

( \ ( f ) ( \ ( x ) f ( x ( x )))( \ ( x ) f ( x ( x )))) ( \ ( f ) \ ( n ) si ( n == 0 ) 1 si no n * f ( n - 1 )) ( 5 )

Esto funciona porque R utiliza evaluación perezosa .

Los lenguajes que utilizan evaluación estricta , como Python , C++ y otros lenguajes de programación estrictos , a menudo pueden expresar Y; sin embargo, cualquier implementación es inútil en la práctica ya que entra en un bucle indefinidamente hasta que termina por un desbordamiento de pila .

Combinador de punto fijo

El combinador Y es una implementación de un combinador de punto fijo en cálculo lambda. Los combinadores de punto fijo también se pueden definir fácilmente en otros lenguajes funcionales e imperativos. La implementación en cálculo lambda es más difícil debido a las limitaciones de este lenguaje. El combinador de punto fijo se puede utilizar en diversas áreas:

Los combinadores de punto fijo pueden aplicarse a diversas funciones, pero normalmente no finalizan su ejecución a menos que haya un parámetro adicional. Cuando la función que se va a fijar hace referencia a su parámetro, se invoca otra llamada a la función, por lo que el cálculo nunca se inicia. En su lugar, el parámetro adicional se utiliza para activar el inicio del cálculo.

El tipo del punto fijo es el tipo de retorno de la función que se está fijando. Este puede ser un número real, una función o cualquier otro tipo.

En el cálculo lambda sin tipos, la función a la que se aplica el combinador de punto fijo puede expresarse mediante una codificación, como la codificación de Church . En este caso, los términos lambda particulares (que definen funciones) se consideran valores. Al aplicar el combinador de punto fijo a la codificación, se obtiene un término lambda para el resultado, que puede interpretarse como un valor de punto fijo.

Alternativamente, una función puede considerarse como un término lambda definido exclusivamente en el cálculo lambda.

Estos diferentes enfoques influyen en cómo un matemático y un programador pueden considerar un combinador de punto fijo. Un matemático puede ver el combinador Y aplicado a una función como una expresión que satisface la ecuación de punto fijo y, por lo tanto, como una solución.

En cambio, una persona que solo quiera aplicar un combinador de punto fijo a alguna tarea de programación general puede verlo simplemente como un medio para implementar la recursión.

Valores y dominios

Muchas funciones no tienen puntos fijos, por ejemploF:nortenorte{\displaystyle f:\mathbb {N} \to \mathbb {N} }conF(norte)=norte+1{\displaystyle f(n)=n+1}Mediante la codificación de Church , los números naturales pueden representarse en el cálculo lambda, y esta función f puede definirse en dicho cálculo. Sin embargo, su dominio ahora contendrá todas las expresiones lambda, no solo las que representan números naturales. El combinador Y, aplicado a f , dará como resultado un punto fijo para f , pero este punto fijo no representará un número natural . Si se intenta calcular Y f en un lenguaje de programación real, se producirá un bucle infinito.

Función versus implementación

El combinador de punto fijo puede definirse en matemáticas y luego implementarse en otros lenguajes. Las matemáticas generales definen una función en función de sus propiedades extensionales . [ 3 ] Es decir, dos funciones son iguales si realizan la misma función. El cálculo lambda y los lenguajes de programación consideran la identidad de una función como una propiedad intensional . La identidad de una función se basa en su implementación.

Una función (o término) del cálculo lambda es una implementación de una función matemática. En el cálculo lambda existen varios combinadores (implementaciones) que satisfacen la definición matemática de un combinador de punto fijo.

Definición del término "combinador"

La lógica combinatoria es una teoría de funciones de orden superior . Un combinador es una expresión lambda cerrada , lo que significa que no tiene variables libres . Los combinadores se pueden combinar para dirigir valores a sus posiciones correctas en la expresión sin necesidad de nombrarlos como variables.

Definiciones recursivas y combinadores de punto fijo

Los combinadores de punto fijo pueden utilizarse para implementar la definición recursiva de funciones. Sin embargo, rara vez se utilizan en la programación práctica. [ 4 ] Los sistemas de tipos fuertemente normalizadores , como el cálculo lambda de tipos simples, no permiten la no terminación y, por lo tanto, a los combinadores de punto fijo a menudo no se les puede asignar un tipo o requieren características complejas del sistema de tipos. Además, los combinadores de punto fijo suelen ser ineficientes en comparación con otras estrategias para implementar la recursión, ya que requieren más reducciones de funciones y construyen y descomponen una tupla para cada grupo de definiciones mutuamente recursivas. [ 1 ] : página 232

La función factorial

La función factorial proporciona un buen ejemplo de cómo se puede utilizar un combinador de punto fijo para definir funciones recursivas. La definición recursiva estándar de la función factorial en matemáticas se puede escribir como

hecho norte={1si norte=0norte×hecho(norte1)de lo contrario.{\displaystyle \operatorname {fact} \ n={\begin{cases}1&{\text{si}}~n=0\\n\times \operatorname {fact} (n-1)&{\text{en otro caso.}}\end{cases}}}

donde n es un entero no negativo .

Esto plantea un problema, sin embargo, ya quehecho{\displaystyle \operatorname {fact} }dentro de la definición dehecho{\displaystyle \operatorname {fact} }No puede significar la inclusión de la definición en su totalidad dentro de sí misma. En cambio, y como suele pasar casi desapercibido, se trata de la referencia a la definición por su nombre . Esto presupone la existencia de un registro global de nombres, el llamado «entorno». Y el cálculo lambda carece por completo de esto.

Sin embargo, en el cálculo lambda existen referencias a nombres: nombres locales, nombres de parámetros de una función dada. Y por lo tanto, lo anterior se puede reescribir como

hecho(norte)=F(F,norte)dóndeF(s,norte)={1si norte=0norte×s(s,norte1)de lo contrario{\displaystyle {\begin{aligned}&\operatorname {fact} (n)=\operatorname {f} (\operatorname {f} ,n)\\&\quad {\text{donde}}\\&\quad \operatorname {f} (s,n)={\begin{cases}1&{\text{si}}~n=0\\n\times s(s,n-1)&{\text{en otro caso}}\end{cases}}\end{aligned}}}

Separar una copia independiente deF{\displaystyle \operatorname {f} }y al pasarlo como argumento a sí mismo, lo pone a su disposición para ser llamado, según sea necesario, dentro de sí mismo. En tal llamada, debe pasarse nuevamente como argumento para que esté disponible para la siguiente invocación, y así sucesivamente, a medida que avanza la cadena de invocaciones. La definición deF{\displaystyle \operatorname {f} }es recursivo abierto y llamandoF{\displaystyle \operatorname {f} }Consistiendo en sí mismo como argumento en la parte superior, se cierra el círculo.

Implementar esto en el cálculo lambda ahora es trivial y conduce directamente a la definición del combinador Y, una instancia de un combinador de punto fijo, tal quearreglarF=F(arreglarF){\displaystyle \operatorname {fix} f=f\,(\operatorname {fix} f)}. O directamente, defina una función F de dos argumentos f y n :

F F norte=(Es cero norte) 1 (multiplicar norte (F (depredador norte))){\displaystyle F\ f\ n=(\operatorname {IsZero} \ n)\ 1\ (\operatorname {multiply} \ n\ (f\ (\operatorname {pred} \ n)))}

(Aquí(Es cero norte){\displaystyle (\operatorname {IsZero} \ n)}es una función que toma dos argumentos y devuelve su primer argumento si n = 0, y su segundo argumento en caso contrario;depredador norte{\displaystyle \operatorname {pred} \ n}se evalúa a n –1.)

Ahora definehecho=arreglar F=F (arreglar F){\displaystyle \operatorname {fact} ={\textsf {fix}}\ F=F\ ({\textsf {fix}}\ F)}. Entonceshecho{\displaystyle \operatorname {fact} }es un punto fijo de F , que da

hechonorte=F hecho norte=(Es cero norte) 1 (multiplicar norte (hecho (depredador norte))) {\displaystyle {\begin{aligned}\operatorname {fact} n&=F\ \operatorname {fact} \ n\\&=(\operatorname {IsZero} \ n)\ 1\ (\operatorname {multiply} \ n\ (\operatorname {fact} \ (\operatorname {pred} \ n)))\ \end{aligned}}}

como se desee.

Combinadores de punto fijo en el cálculo lambda

El combinador Y, descubierto por Haskell Curry , se define como

Y=λF.(λincógnita.F (incógnita incógnita)) (λincógnita.F (incógnita incógnita)){\displaystyle Y=\lambda f.(\lambda xf\ (x\ x))\ (\lambda xf\ (x\ x))}

Otros combinadores de punto fijo

En el cálculo lambda sin tipos, los combinadores de punto fijo no son especialmente raros. De hecho, hay infinitos. [ 5 ] En 2005, Mayer Goldberg demostró que el conjunto de combinadores de punto fijo del cálculo lambda sin tipos es recursivamente enumerable . [ 6 ]

El combinador Y se puede expresar en el cálculo SKI como

Y=S(K(SII))(S(S(KS)K)(K(SII)))=SS(S(S(KS)K))(K(SII)){\displaystyle {\mathsf {Y=S(K(SII))(S(S(KS)K)(K(SII)))=SS(S(S(KS)K))(K(SII))}}}

Los combinadores adicionales ( sistema B, C, K, W ) permiten codificaciones mucho más cortas. ConU=SII{\displaystyle {\mathsf {U=SII}}}el combinador de autoaplicación, ya queS(Kincógnita)yz=incógnita(yz)=Bincógnitayz{\displaystyle {\mathsf {S}}({\mathsf {K}}x)yz=x(yz)={\mathsf {B}}xyz}ySincógnita(Ky)z=incógnitazy=doincógnitayz{\displaystyle {\mathsf {S}}x({\mathsf {K}}y)z=xzy={\mathsf {C}}xyz}, lo anterior se convierte en

Y=S(KU)(SB(KU))=BU(doBU)   ;  Y=SSI(BWB){\displaystyle {\mathsf {Y=S(KU)(SB(KU))=BU(CBU)}}\ \ \ ;\ \ {\mathsf {Y=SSI(BWB)}}}

El combinador de punto fijo más corto en el cálculo SK que utiliza solo combinadores S y K, hallado por John Tromp , es

Y=SSK(S(K(SS(S(SSK))))K)=Wdo(SB(do(Wdo))){\displaystyle {\mathsf {Y'=SSK(S(K(SS(S(SSK))))K)=WC(SB(C(WC)))}}}

aunque tenga en cuenta que no está en forma normal, que es más larga. Este combinador corresponde a la expresión lambda.

Y=(λincógnitay.incógnitayincógnita)(λyincógnita.y(incógnitayincógnita)){\displaystyle {\mathsf {Y}}'=(\lambda xy.xyx)(\lambda yx.y(xyx))}

El siguiente combinador de punto fijo es más simple que el combinador Y, y se reduce mediante β al combinador Y; a veces se le cita como el propio combinador Y:

incógnita=λF.(λincógnita.incógnitaincógnita)(λincógnita.F(incógnitaincógnita))   ;  incógnitaF=U(BFU){\displaystyle {\mathsf {X}}=\lambda f.(\lambda x.xx)(\lambda xf(xx))\ \ \ ;\ \ {\mathsf {Xf=U(BfU)}}}

Otro combinador de punto fijo común es el combinador de punto fijo de Turing (llamado así por su descubridor, Alan Turing ): [ 7 ] [ 2 ] : 132

Θ=(λincógnitay.y(incógnitaincógnitay)) (λincógnitay.y(incógnitaincógnitay))=SII(S(K(SI))(SII))=U(B(SI)U){\displaystyle \Theta =(\lambda xy.y(xxy))\ (\lambda xy.y(xxy))={\mathsf {SII(S(K(SI))(SII))=U(B(SI)U)}}}

Su ventaja sobreY{\displaystyle {\mathsf {Y}}}es queΘ F{\displaystyle \Theta \ f}beta-reduce aF (ΘF){\displaystyle f\ (\Theta f)}, [ nota 3 ] mientras queY F{\displaystyle {\mathsf {Y}}\ f}yF (YF){\displaystyle f\ ({\mathsf {Y}}f)}solo beta-reducción a un término común.

Θ{\displaystyle \Theta }También tiene una forma sencilla de llamada por valor :

Θv=(λincógnitay.y(λz.incógnitaincógnitayz)) (λincógnitay.y(λz.incógnitaincógnitayz)){\displaystyle \Theta _{v}=(\lambda xy.y(\lambda z.xxyz))\ (\lambda xy.y(\lambda z.xxyz))}

El análogo de la recursión mutua es un combinador de punto fijo polivariádico , [ 8 ] [ 9 ] [ 10 ] que puede denotarse como Y*.

Combinador de punto fijo estricto

En un lenguaje de programación estricto, el combinador Y se expandirá hasta desbordamiento de pila , o nunca se detendrá en caso de optimización de llamada de cola . [ 11 ] El combinador Z funcionará en lenguajes estrictos (también llamados lenguajes ansiosos, donde se aplica el orden de evaluación aplicativo). El combinador Z tiene el siguiente argumento definido explícitamente, lo que impide la expansión deZgramo{\displaystyle Zg}en el lado derecho de la definición: [ 12 ]

Zgramov=gramo(Zgramo)v .{\displaystyle Zgv=g(Zg)v\ .}

y en el cálculo lambda es una expansión eta del combinador Y:

Z=λF.(λincógnita.F(λv.incógnitaincógnitav)) (λincógnita.F(λv.incógnitaincógnitav)) .{\displaystyle Z=\lambda f.(\lambda x.f(\lambda v.xxv))\ (\lambda x.f(\lambda v.xxv))\ .}

Combinadores de punto fijo no estándar

Si F es un combinador de punto fijo en el cálculo lambda sin tipos, entonces se cumple que:

F=λincógnita.Fincógnita=λincógnita.incógnita(Fincógnita)=λincógnita.incógnita(incógnita(Fincógnita))={\displaystyle {\mathsf {F}}=\lambda x.Fx=\lambda x.x(Fx)=\lambda x.x(x(Fx))=\cdots }

Términos que tienen el mismo árbol de Böhm que un combinador de punto fijo, es decir, tienen la misma extensión infinita.λincógnita.incógnita(incógnita(incógnita)){\displaystyle \lambda x.x(x(x\cdots ))}Se denominan combinadores de punto fijo no estándar . Cualquier combinador de punto fijo es también uno no estándar, pero no todos los combinadores de punto fijo no estándar son combinadores de punto fijo, ya que algunos no satisfacen la ecuación de punto fijo que define a los "estándar". Estos combinadores se denominan combinadores de punto fijo estrictamente no estándar ; un ejemplo es el siguiente combinador:

norte=BU(B(BU)B){\displaystyle {\mathsf {N=BU(B(BU)B)}}}

dónde

B=λincógnitayz.incógnita(yz){\displaystyle {\mathsf {B}}=\lambda xyz.x(yz)}
U=λincógnita.incógnitaincógnita {\displaystyle {\mathsf {U}}=\lambda x.xx\ }

desde

norte=λincógnita.norteincógnita=λincógnita.incógnita(norte2incógnita)=λincógnita.incógnita(incógnita(incógnita(norte3incógnita)))=λincógnita.incógnita(incógnita(incógnita(incógnita(incógnita(incógnita(norte4incógnita))))))={\displaystyle {\mathsf {N}}=\lambda x.Nx=\lambda x.x(N_{2}x)=\lambda x.x(x(x(N_{3}x)))=\lambda x.x(x(x(x(x(x(N_{4}x))))))=\cdots }

dóndenortei{\displaystyle {\mathsf {N}}_{i}}son modificaciones denorte{\displaystyle {\mathsf {N}}}creados sobre la marcha que añadeni{\displaystyle i}casos deincógnita{\displaystyle x}inmediatamente en la cadena mientras se reemplaza connortei+1{\displaystyle {\mathsf {N}}_{i+1}}.

El conjunto de combinadores de punto fijo no estándar no es recursivamente enumerable . [ 6 ]

Implementación en otros idiomas

El combinador Y es una implementación particular de un combinador de punto fijo en el cálculo lambda. Su estructura está determinada por las limitaciones de este cálculo. No es necesario ni útil utilizar esta estructura para implementar el combinador de punto fijo en otros lenguajes.

A continuación se muestran ejemplos sencillos de combinadores de punto fijo implementados en algunos paradigmas de programación .

Implementación funcional perezosa

En un lenguaje que admite evaluación perezosa , como Haskell , es posible definir un combinador de punto fijo utilizando la ecuación definitoria del combinador de punto fijo que convencionalmente se denomina fix. Dado que Haskell tiene tipos de datos perezosos , este combinador también se puede usar para definir puntos fijos de constructores de datos (y no solo para implementar funciones recursivas). La definición se da aquí, seguida de algunos ejemplos de uso. En Hackage, el ejemplo original es: [ 13 ]

fix , fix' :: ( a -> a ) -> a fix f = let x = f x in x -- Lambda eliminada. Compartición. -- Definición original en Data.Function. -- alternativa: fix' f = f ( fix' f ) -- Lambda levantada. No compartición.corregir ( \ x -> 9 ) -- esto se evalúa como 9fix ( \ x -> 3 : x ) -- se evalúa a la lista infinita perezosa [3,3,3,...]hecho = fijo fac -- se evalúa a la función factorial donde fac f 0 = 1 fac f x = x * f ( x - 1 )Dato 5 : se evalúa a 120.

Implementación funcional estricta

En un lenguaje funcional estricto, como se ilustra a continuación con OCaml , el argumento de f se expande de antemano, lo que produce una secuencia de llamadas infinita,

F (F...(F (Fiincógnita F))...) incógnita{\displaystyle f\ (f...(f\ ({\mathsf {fix}}\ f))...)\ x}.

Esto puede resolverse definiendo la corrección con un parámetro adicional.

sea ​​rec fix f x = f ( fix f ) x (* observe la x adicional; por lo tanto fix f = \x-> f (fix f) x *)let factabs fact = function (* factabs tiene un nivel adicional de abstracción lambda *) 0 -> 1 | x -> x * fact ( x - 1 )let _ = ( fix factabs ) 5 (* se evalúa a "120" *)

En un lenguaje funcional multiparadigma (uno decorado con características imperativas), como Lisp , Peter Landin sugirió el uso de una asignación de variables para crear un combinador de punto fijo, [ 14 ] como en el siguiente ejemplo usando Scheme :

( define Y! ( lambda ( f ) (( lambda ( g ) ( set! g ( f ( lambda ( x ) ( g x )))) ;; (set! g expr) asigna a g el valor de expr, g ) ;; reemplazando el valor inicial de g de #f, creando #f ))) ;; por lo tanto, el valor verdaderamente autorreferencial en g

Utilizando un cálculo lambda con axiomas para sentencias de asignación, se puede demostrar que Y!satisface la misma ley de punto fijo que el combinador Y de llamada por valor: [ 15 ] [ 16 ]

(Y¡ λincógnita.mi)mi=(λincógnita.mi) (Y¡ λincógnita.mi)mi{\displaystyle (Y_{!}\ \lambda x.e)e'=(\lambda x.e)\ (Y_{!}\ \lambda x.e)e'}

En el uso más idiomático de Scheme moderno, esto normalmente se manejaría a través de una letrecexpresión, ya que el alcance léxico se introdujo en Lisp en la década de 1970:

( define Y* ( lambda ( f ) ( letrec ;; (letrec ((g expr)) ...) define localmente g (( g ;; como expr recursivamente: g en expr se refiere a ( f ( lambda ( x ) ( g x ))))) ;; esa misma g definida, g = f (λx. gx) g ))) ;; ((Y* f) ...) = (g ...) = ((f (λx. gx)) ...)

O sin la etiqueta interna:

( define Y* ( lambda ( f ) (( lambda ( i ) ( i i )) ( lambda ( i ) ( f ( lambda x ( apply ( i i ) x )))))))

Implementación del lenguaje imperativo

Este ejemplo es una implementación ligeramente interpretativa de un combinador de punto fijo. Se utiliza una clase para contener la fix()función, llamada FixedPointCombinator. La función que se va a corregir está contenida en una clase que hereda de fixer. La fix()función accede a la función que se va a corregir usando un concepto para llamar a apply(). En cuanto a la definición funcional estricta, fix()se le da explícitamente un parámetro adicional x, lo que significa que no se necesita evaluación perezosa.

usando std :: same_as ;plantilla < typename Ret , typename Arg , typename T > concepto FixedPointApplicable = requiere ( Arg x ) { { T :: apply ( x ) } -> same_as < Ret > ; };plantilla < typename Ret , typename Arg , FixedPointApplicable < Ret , Arg > Derived > clase FixedPointCombinator { public : static Ret fix ( Arg x ) noexcept { return Derived :: apply ( x ); } };clase Factorial : public FixedPointCombinator < long , long , Factorial > { static long apply ( long x ) noexcept { if ( x == 0 ) { return 1 ; } return x * fix ( x - 1 ); } };resultado largo = Factorial :: fijar ( 5 );

Utilizando únicamente funciones lambda, se puede crear un combinador de punto fijo como este:

auto fix = []( auto f ) { return [ f ]( auto && ... args ) -> decltype ( auto ) { return f ( f , std :: forward < decltype ( args ) > ( args )...); }; };auto factorial = fix ([]( auto self , long n ) -> long { return n == 0 ? 1 : n * self ( self , n - 1 ); });std :: println ( "5! = {}" , factorial ( 5 )); // imprime 120

Se puede mostrar otro ejemplo para demostrar el cálculo del combinador SKI (con el nombre del pájaro dado de la lógica combinatoria ) que se utiliza para construir el combinador Z para lograr un comportamiento similar a una llamada de cola a través del trampolín:

// Combinadores const K = < A , B > ( a : A ) => ( _b : B ) => a ; // Cernícalo const S = < A , B , C > ( a : ( x : C ) => ( y : B ) => A ) => ( b : ( x : C ) => B ) => ( c : C ) => a ( c )( b ( c )); // Estornino// Combinadores derivados const I = S ( K )( K ); // Identidad const B = S ( K ( S ))( K ); // Azulejo const C = S ( B ( B )( S ))( K ( K )); // Cardenal const W = C ( S )( I ); // Reinita const T = C ( I ); // Zorzal const V = B ( C )( T ); // Vireo const I1 = C ( C ( I )); // Ave Identidad una vez eliminada; igual que C(B(B)(I))(I) const C1 = B ( C ); // Cardenal una vez eliminada const R1 = C1 ( C1 ); // Petirrojo una vez eliminada const V1 = B ( R1 )( C1 ); // Vireo una vez eliminada const I2 = R1 ( V ); // Ave Identidad dos veces eliminada// Combinadores Z const Z = B ( W ( I1 ))( V1 ( B )( W ( I2 )));const Z2 = S ( K ( S ( S ( K ( S ( S ( K )( K ))( S ( K )( K ))))( S ( K ( S ( K ( S ))( K )))( S ( K ( S ( S ( K )( K ))))( K ))))( K ( S ( S ( S ( K ))))))( S ( S ( K ( S ( S ( S ( K ( S ( K ( S ))( K )))( S ))( K ( K ))))( S ( K ( S ( S ( K ( S ( K ( S ) )( K )) )( S ))( K ( K ))))( S ( K ( S ))( K ))))( K ( S ( S ( K ( S ( S ( K )( K ))( S ( K )( K ))))( S ( K ( S ( K ( S ))( K )))( S ( K ( S ( S ( K )( K ))))( K ))))( K ( S ( K ( S ( S ( K ( S ( S ( K ( S ( K ( S ) )( K )) ) ( S ))( K ( K ))))( S ( K ( S ( S (K ( S ( K ( S ))( K ) ))( S ))( K ( K ))))( S ( K ( S ( S ( K )( K ))))( K ))))))( K )))))); // Forma alternativa totalmente expandida.const Z3 = S ( S ( K ( S ( S )( K ( S ( S ( K )( K ))( S ( K )( K ))))))( K ))( S ( S ( K ( S )) ( K ))( K ( S ( S ( K ( S ))( S ( K ( S ( K ( S ( K ( S ( S ) ( K ( K ) )))( K )))( S )))( S ( S ( K )( K ))))) ) ( K ) ))( K ( K ( S ( S ( K ) ( K ))( S ( K )( K )))))))); // Otra versión más corta.const trampolín = < T > ( fn : T | (() => T )) : T => { let ctx = fn ; while ( typeof ctx === "function" ) { ctx = ( ctx as () => T )(); } return ctx ; };const countFn = ( self : ( n : number ) => any ) => ( n : number ) : any => n === 0 ? ( console . log ( n ), n ) : () => self ( n - 1 ); // Devuelve thunk "() => self(n - 1)" en su lugar.// Ejemplos trampolín ( Z ( countFn )( 10 )); trampolín ( Z2 ( countFn )( 10 )); trampolín ( Z3 ( countFn )( 10 ));

Mecanografía

En el sistema F ( cálculo lambda polimórfico ), un combinador de punto fijo polimórfico tiene tipo [ 17 ].

∀a.(a → a) → a

dóndea{\displaystyle a}es una variable de tipo . Es decir, si el tipo deFiincógnita F{\displaystyle \mathrm {fix} \ f}cumpliendo la ecuaciónFiincógnita F = F (Fiincógnita F){\displaystyle \mathrm {fix} \ f\ =\ f\ (\mathrm {fix} \ f)}esa{\displaystyle a}—el tipo más general—luego el tipo deF{\displaystyle f}esaa{\displaystyle a\to a}Entonces,Fiincógnita{\displaystyle \mathrm {fix} }toma una función que mapeaa{\displaystyle a}aa{\displaystyle a}y lo utiliza para devolver un valor de tipoa{\displaystyle a}.

En el cálculo lambda de tipos simples extendido con tipos de datos recursivos , se pueden escribir operadores de punto fijo, pero el tipo de un operador de punto fijo "útil" (aquel cuya aplicación siempre devuelve un valor) puede estar restringido.

En el cálculo lambda simplemente tipado , al combinador de punto fijo Y no se le puede asignar un tipo [ 18 ] porque en algún momento trataría con el subtérmino de autoaplicación.incógnita incógnita{\displaystyle x~x}por la regla de aplicación:

Γincógnita:t1t2Γincógnita:t1Γincógnita incógnita:t2{\displaystyle {\Gamma \vdash x\!:\!t_{1}\to t_{2}\quad \Gamma \vdash x\!:\!t_{1}} \over {\Gamma \vdash x~x\!:\!t_{2}}}

dóndeincógnita{\displaystyle x}tiene el tipo infinitot1=t1t2{\displaystyle t_{1}=t_{1}\to t_{2}}Ningún combinador de punto fijo puede tipificarse; en esos sistemas, cualquier soporte para la recursión debe agregarse explícitamente al lenguaje.

Tipo para el combinador Y

En los lenguajes de programación que admiten tipos de datos recursivos con nombre , la recursión ilimitada ent:=ta{\displaystyle t:=t\to a}, lo que crea el tipo que sería infinitot{\displaystyle t}, se rompe al nombrar el tipot{\displaystyle t}explícitamente, como por ejemplo tipoR a{\displaystyle R\ a}que se define de manera que sea isomorfo a (o simplemente sinónimo de) el tipoR aa{\displaystyle R\ a\to a}. De este modoR a:=R aa{\displaystyle R\ a:=R\ a\to a}se reconoce como un tipo recursivo. Un dato (valor) del tipoR a{\displaystyle R\ a}se crea simplemente etiquetando un valor que es una función que tiene el tipoR aa{\displaystyle R\ a\to a}, por la etiqueta del constructor de datos para el tipoR a{\displaystyle R\ a}.

Por ejemplo, en el siguiente código Haskell, sea Recese nombre de etiqueta, y por lo tanto Recy appsean los nombres de las dos direcciones del isomorfismo, con tipos: [ 19 ] [ 20 ]

Rec :: ( R a -> a ) -> R a app :: R a -> ( R a -> a )

(dónde::{\displaystyle ::} significa "tiene tipo"). Esto nos permite escribir:

nuevo tipo R a = Rec { app :: R a -> a } -- app (Rec g) = g -- g :: R a -> a -- Rec g :: R a -- app :: R a -> (R a -> a)y :: ( a -> a ) -> a y f = ( \ x -> f ( app x x )) ( Rec ( \ x -> f ( app x x ))) -- x :: R a -- app x :: R a -> a -- app xx :: a

La definición habitual del cálculo lambdaYF=U(λincógnita.F (Uincógnita)){\displaystyle \operatorname {Y} f=\operatorname {U} (\lambda x.f\ (\operatorname {U} x))}(dóndeUincógnita=incógnita incógnita{\displaystyle \operatorname {U} x=x\ x}y por lo tanto el términoU(λincógnita.F (Uincógnita)){\displaystyle \operatorname {U} (\lambda x.f\ (\operatorname {U} x))}testigos de la equivalenciaincógnita incógnita=F (incógnita incógnita){\displaystyle x\ x=f\ (x\ x)}) contiene en su núcleo la autoaplicaciónincógnita incógnita{\displaystyle x\ x}, que no es tipificable en los lenguajes de programación de tipado estático. Pero la expresión app x xanterior sí es tipificable. De forma equivalente, podemos redefinir el combinador de autoaplicación.U{\displaystyle \operatorname {U} }y usarlo como

U' g = g ( Rec g ) y f = U' ( \ Rec g -> f ( U' g )) -- g (Rec g) = f (g (Rec g))U'' x = app x x y f = U'' ( Rec ( \ x -> f ( U'' x ))) -- app xx = f (app xx)

O en OCaml:

tipo ' a recc = En de ( ' a recc -> ' a ) dejar fuera ( En x ) = xsea ​​y f = ( fun x a -> f ( out x x ) a ) ( In ( fun x a -> f ( out x x ) a ))

Alternativamente:

sea ​​y f = ( fun x -> f ( fun z -> out x x z )) ( In ( fun x -> f ( fun z -> out x x z )))

Algunos lenguajes permiten marcar explícitamente tipos generales como recursivos incluso sin nombrarlos. Hacer eso también permite definir elY{\displaystyle \operatorname {Y} }combinador en dichos lenguajes.

información general

Dado que los combinadores de punto fijo pueden utilizarse para implementar la recursión, es posible usarlos para describir tipos específicos de cálculos recursivos, como los de la iteración de punto fijo , los métodos iterativos , la unión recursiva en bases de datos relacionales , el análisis de flujo de datos , los conjuntos FIRST y FOLLOW de no terminales en una gramática libre de contexto , el cierre transitivo y otros tipos de operaciones de cierre .

Una función para la cual cada entrada es un punto fijo se denomina función identidad . Formalmente:

incógnita(F incógnita=incógnita){\displaystyle \forall x(f\ x=x)}

En contraste con la cuantificación universal sobre todosincógnita{\displaystyle x}, un combinador de punto fijo construye un valor que es un punto fijo deF{\displaystyle f}La propiedad más destacada de un combinador de punto fijo es que construye un punto fijo para una función dada arbitraria.F{\displaystyle f}.

Otras funciones tienen la propiedad especial de que, después de aplicarse una vez, las aplicaciones posteriores no tienen ningún efecto. De manera más formal:

incógnita(F (F incógnita)=F incógnita){\displaystyle \forall x(f\ (f\ x)=f\ x)}

Estas funciones se denominan idempotentes (véase también Proyección (matemáticas) ). Un ejemplo de este tipo de función es aquella que devuelve 0 para todos los números pares y 1 para todos los números impares.

En el cálculo lambda , desde un punto de vista computacional, aplicar un combinador de punto fijo a una función identidad o una función idempotente generalmente resulta en un cálculo que no termina. Por ejemplo, obtener

(Y λincógnita.incógnita)=(λincógnita.(λincógnita.incógnita)(incógnita incógnita)) (λincógnita.(λincógnita.incógnita)(incógnita incógnita)){\displaystyle (\mathrm {Y} \ \lambda x.x)=(\lambda x.(\lambda x.x)(x\ x))\ (\lambda x.(\lambda x.x)(x\ x))}

donde el término resultante solo puede reducirse a sí mismo y representa un bucle infinito.

Los combinadores de punto fijo no necesariamente existen en modelos de computación más restrictivos. Por ejemplo, no existen en el cálculo lambda simplemente tipado .

El combinador Y permite definir la recursión como un conjunto de reglas de reescritura , [ 21 ] sin requerir soporte nativo para la recursión en el lenguaje. [ 22 ]

En los lenguajes de programación que admiten funciones anónimas , los combinadores de punto fijo permiten definir y usar funciones recursivas anónimas , es decir, sin tener que vincular dichas funciones a identificadores . En este contexto, el uso de combinadores de punto fijo se denomina a veces recursión anónima . [ nota 4 ] [ 23 ]

Véase también

Notas

  1. A lo largo de este artículo, se utilizanlas reglas de sintaxis dadas en la definición de cálculo lambda §  Notación , para ahorrar paréntesis.
  2. Según Barendregt, pág. 132, el nombre tiene su origen en Curry.
  3. Θ F{\displaystyle \Theta \ f}{\displaystyle \equiv }(λincógnitay.y(incógnitaincógnitay)) (λincógnitay.y(incógnitaincógnitay)) F{\displaystyle (\lambda xy.y(xxy))\ (\lambda xy.y(xxy))\ f}{\displaystyle \to }(λy.y ((λincógnitay.y(incógnitaincógnitay)) (λincógnitay.y(incógnitaincógnitay)) y)) F{\displaystyle (\lambda y.y\ ((\lambda xy.y(xxy))\ (\lambda xy.y(xxy))\ y))\ f}{\displaystyle \to }F ((λincógnitay.y(incógnitaincógnitay)) (λincógnitay.y(incógnitaincógnitay)) F){\displaystyle f\ ((\lambda xy.y(xxy))\ (\lambda xy.y(xxy))\ f)}{\displaystyle \equiv }F (Θ F){\displaystyle f\ (\Theta \ f)}
  4. Esta terminología parece ser en gran parte [[folclore matemático|]], pero aparece en:
    • Trey Nash, Accelerated C# 2008 , Apress, 2007, ISBN 1-59059-873-3, págs. 462-463. Basado sustancialmente en el blog de Wes Dyer (véase el siguiente punto).
    • El artículo de Wes Dyer, "Recursión anónima en C#" , del 2 de febrero de 2007, contiene un ejemplo sustancialmente similar al que se encuentra en el libro mencionado anteriormente, pero acompañado de una explicación más detallada.

Referencias

  1. 1 2 Peyton Jones, Simon L. (1987). La implementación de la programación funcional (PDF) . Prentice Hall International.
  2. 1 2 Henk Barendregt (1985). El cálculo lambda : su sintaxis y semántica . Estudios de lógica y fundamentos de las matemáticas. Vol. 103. Ámsterdam: North-Holland. ISBN  0444867481.
  3. Selinger, Peter (2001–2013). "Apuntes de clase sobre el cálculo lambda: notas de curso expositivas" (PDF) . pág. 6. 
  4. "Para aquellos de nosotros que no sabemos qué es un Y-Combinator o por qué es útil,..." Hacker News . Consultado el 2 de agosto de 2020 .
  5. Bimbó, Katalin (27 de julio de 2011). Lógica combinatoria: pura, aplicada y tipificada . CRC Press. pág. 48. ISBN  9781439800010.
  6. 1 2 Goldberg, 2005
  7. Alan Mathison Turing (diciembre de 1937). "Elpag{\displaystyle p}-función enλ{\displaystyle \lambda }-K{\displaystyle K}-conversión". Revista de Lógica Simbólica . 2 (4): 164. JSTOR 2268281 . 
  8. "Muchas caras del combinador de punto fijo" . okmij.org .
  9. Y polivariádica en Haskell98 puro Archivado el 4 de marzo de 2016 en Wayback Machine , lang.haskell.cafe, 28 de octubre de 2003
  10. "recursión - ¿Combinador de punto fijo para funciones mutuamente recursivas?" . Stack Overflow .
  11. Bene, Adam (17 de agosto de 2017). "Combinadores de punto fijo en JavaScript" . Bene Studio . Consultado el 2 de agosto de 2020 .
  12. "CS 6110 S17 Lección 5. Recursión y combinadores de punto fijo" (PDF) . Universidad de Cornell . 4.1 Un combinador de punto fijo CBV.
  13. La definición original en Data.Function .
  14. ^ Landin, PJ (enero de 1964). "La evaluación mecánica de las expresiones". La revista informática . 6 (4): 308– 320. doi : 10.1093/comjnl/6.4.308 .
  15. ^ Felleisen, Matías (1987). El cálculo Lambda-v-CS . Universidad de Indiana.
  16. Talcott, Carolyn (1985). La esencia del ron: una teoría de los aspectos intensionales y extensionales de la computación de tipo Lisp (tesis doctoral). Universidad de Stanford.
  17. Girard, Jean-Yves (1986). "El sistema F de tipos variables, quince años después". Theoretical Computer Science . 45 (2): 159– 192. doi : 10.1016/0304-3975(86)90044-7 . MR 0867281 . Véase especialmente la página 180.
  18. Introducción al cálculo lambda Archivado el 8 de abril de 2014 en Wayback Machine
  19. Hilo de la lista de correo de Haskell sobre cómo definir el combinador Y en Haskell , 15 de septiembre de 2006
  20. ^ Geuvers, Herman; Verkoelen, Joep, Sobre combinadores de punto fijo y bucles en la teoría de tipos (PDF) , CiteSeerX 10.1.1.158.1478 
  21. Friedman, Daniel P. ; Felleisen, Matthias (1986). "Capítulo 9 - Lambda The Ultimate". The Little Lisper . Science Research Associates . p. 179. En el capítulo hemos derivado un combinador Y que nos permite escribir funciones recursivas de un argumento sin usar define. 
  22. Vanier, Mike (14 de agosto de 2008). "The Y Combinator (Slight Return) or: How to Succeed at Recursion Without Really Recursing" . Archivado del original el 22 de agosto de 2011. En términos más generales, Y nos ofrece una forma de obtener recursión en un lenguaje de programación que admite funciones de primera clase, pero que no tiene la recursión incorporada.
  23. El método If Works para derivar el combinador Y , 10 de enero de 2008
  • Werner Kluge, Máquinas de computación abstractas: una perspectiva del cálculo lambda , Springer, 2005, ISBN 3-540-21146-2págs.  73–77
  • Mayer Goldberg, (2005) Sobre la enumerabilidad recursiva de los combinadores de punto fijo , Informe BRICS RS-05-1, Universidad de Aarhus
  • Matthias Felleisen , Una conferencia sobre el porqué de Y.
  • Teoría de la recursión y la alegría , Manfred von Thun, (2002 o anterior)
  • El cálculo lambda - apuntes de Don Blaheta, 12 de octubre de 2000
  • Y Combinator archivado el 23 de marzo de 2009 en Wayback Machine.
  • "Un uso del combinador Y en Ruby"
  • "Programación funcional en Ada"
  • Código Rosetta - Combinador Y