Translate

lunes, 1 de julio de 2024

Funciones anónimas en Erlang


Las funciones anónimas, o funs, permiten declarar un tipo especial de función en línea, sin nombrarlas. Pueden hacer prácticamente todo lo que pueden hacer las funciones normales, excepto llamarse a sí mismas de forma recursiva (¿cómo podrían hacerlo si son anónimas?). Su sintaxis es:

fun(Args1) -> Expression1, Exp2, ..., ExpN;

     (Args2) -> Expression1, Exp2, ..., ExpN;

     (Args3) -> Expression1, Exp2, ..., ExpN

end


Y se puede utilizar de la siguiente manera:


7> Fn = fun() -> a end.

#Fun<erl_eval.20.67289768>

8> Fn().

a

9> hhfuns:map(fun(X) -> X + 1 end, L).

[2,3,4,5,6]

10> hhfuns:map(fun(X) -> X - 1 end, L).

[0,1,2,3,4]


Y ahora estás viendo una de las cosas que hace que a la gente le guste tanto la programación funcional: la capacidad de hacer abstracciones en un nivel muy bajo de código. De este modo, se pueden ignorar conceptos básicos como los bucles, lo que le permite centrarse en lo que se hace en lugar de en cómo hacerlo.

Las funciones anónimas ya son bastante buenas para este tipo de abstracciones, pero aún tienen más poderes ocultos:


11> PrepareAlarm = fun(Room) ->
11>                     io:format("Alarm set in ~s.~n",[Room]),
11>                     fun() -> io:format("Alarm tripped in ~s! Call Batman!~n",[Room]) end
11>                   end.
#Fun<erl_eval.20.67289768>
12> AlarmReady = PrepareAlarm("bathroom").
Alarm set in bathroom.
#Fun<erl_eval.6.13229925>
13> AlarmReady().
Alarm tripped in bathroom! Call Batman!
ok


¡Sostén el teléfono Batman! ¿Que está pasando aqui? Bueno, antes que nada, declaramos una función anónima asignada a PrepareAlarm. Esta función aún no se ha ejecutado: solo se ejecuta cuando PrepareAlarm("baño"). se llama. En ese momento se evalúa la llamada a io:format/2 y se emite el texto "Alarma configurada". La segunda expresión (otra función anónima) se devuelve a la persona que llama y luego se asigna a AlarmReady. Tenga en cuenta que en esta función, el valor de la variable Habitación se toma de la función 'principal' (PrepareAlarm). Esto está relacionado con un concepto llamado cierres.

Para entender los cierres, primero hay que entender el alcance. El alcance de una función se puede imaginar como el lugar donde se almacenan todas las variables y sus valores. En la función base(A) -> B = A + 1., A y B están definidos como parte del alcance de base/1. Esto significa que en cualquier lugar dentro de base/1, puede hacer referencia a A y B y esperar que se les vincule un valor. Y cuando digo "en cualquier lugar", no bromeo, chico; Esto también incluye funciones anónimas:


base(A) ->

     B = A + 1,

     F = fun() -> A * B end,

     F().


B y A todavía están vinculados al alcance de base/1, por lo que la función F aún puede acceder a ellos. Esto se debe a que F hereda el alcance de base/1. Como la mayoría de los tipos de herencia en la vida real, los padres no pueden obtener lo que tienen los hijos:


base(A) ->

    B = A + 1,

    F = fun() -> C = A * B end,

    F(),

    C.


En esta versión de la función, B sigue siendo igual a A + 1 y F seguirá ejecutándose bien. Sin embargo, la variable C solo está en el alcance de la función anónima en F. Cuando base/1 intenta acceder al valor de C en la última línea, solo encuentra una variable independiente. De hecho, si hubiera intentado compilar esta función, el compilador habría dado un error. La herencia sólo va en un sentido.

Es importante tener en cuenta que el alcance heredado sigue a la función anónima dondequiera que esté, incluso cuando se pasa a otra función:


a() ->

     Secret = "pony",

    fun() -> Secret end.

 

b(F) ->

    "a/0's password is "++F().


Entonces si la compilamos:

14> c(hhfuns).
{ok, hhfuns}
15> hhfuns:b(hhfuns:a()).
"a/0's password is pony"

¿Quién le dijo la contraseña a/0? Bueno, a/0 lo hizo. Si bien la función anónima tiene el alcance de a/0 cuando se declara allí, aún puede transportarla cuando se ejecuta en b/1, como se explicó anteriormente. Esto es muy útil porque nos permite transportar parámetros y contenido fuera de su contexto original, donde todo el contexto ya no es necesario (exactamente como hicimos con Batman en un ejemplo anterior).

Es más probable que utilices funciones anónimas para transportar el estado cuando tienes funciones definidas que toman muchos argumentos, pero tienes uno constante:


16> math:pow(5,2).
25.0
17> Base = 2.
2
18> PowerOfTwo = fun(X) -> math:pow(Base,X) end.
#Fun<erl_eval.6.13229925>
17> hhfuns:map(PowerOfTwo, [1,2,3,4]).
[2.0,4.0,8.0,16.0]

Al envolver la llamada a math:pow/2 dentro de una función anónima con la variable Base vinculada a su alcance, hicimos posible que cada una de las llamadas a PowerOfTwo en hhfuns:map/2 usara los números enteros de la lista como exponentes. de nuestra base.

Una pequeña trampa en la que puedes caer al escribir funciones anónimas es cuando intentas redefinir el alcance:

base() ->
    A = 1,
   (fun() -> A = 2 end)().

Esto declarará una función anónima y luego la ejecutará. Como la función anónima hereda el alcance de base/0, al intentar utilizar el operador = se compara 2 con la variable A (vinculada a 1). Está garantizado que esto fracasará. Sin embargo, es posible redefinir la variable si se hace en el encabezado de la función anidada:

base() ->
    A = 1,
   (fun(A) -> A = 2 end)(2).


Y esto funciona. Si intenta compilarlo, recibirá una advertencia sobre el sombreado ("Advertencia: variable 'A' sombreada en 'diversión'"). El sombreado es el término utilizado para describir el acto de definir una nueva variable que tiene el mismo nombre que una que estaba en el ámbito principal. Esto está ahí para evitar algunos errores (generalmente con razón), por lo que es posible que desee considerar cambiar el nombre de sus variables en estas circunstancias.

A partir de la versión 17.0, el lenguaje admite el uso de funciones anónimas con un nombre interno. Así es, funciones anónimas pero con nombre.

El truco es que el nombre es visible sólo dentro del alcance de la función, no fuera de ella. La principal ventaja de esto es que permite definir funciones recursivas anónimas. Por ejemplo, podríamos crear una función anónima que siga siendo ruidosa para siempre:

18> f(PrepareAlarm), f(AlarmReady).
ok
19> PrepareAlarm = fun(Room) ->
19>    io:format("Alarm set in ~s.~n",[Room]),
19>     fun Loop() ->
19>        io:format("Alarm tripped in ~s! Call Batman!~n",[Room]),
19>        timer:sleep(500),
19>         Loop()
19>     end
19> end.
#Fun<erl_eval.6.71889879>
20> AlarmReady = PrepareAlarm("bathroom").
Alarm set in bathroom.
#Fun<erl_eval.44.71889879>
21> AlarmReady().
Alarm tripped in bathroom! Call Batman!
Alarm tripped in bathroom! Call Batman!
Alarm tripped in bathroom! Call Batman!
...

La variable Loop se refiere a la función anónima en sí y, dentro de ese alcance, se podrá utilizar como cualquier otra variable similar que apunte a una función anónima. 

Dejaremos un poco de lado la teoría de funciones anónimas y exploraremos abstracciones más comunes para evitar tener que escribir más funciones recursivas.