¿En qué se diferencian los actores de Erlang de los objetos OOP?

Supongamos que tengo un actor de Erlang definido así:

counter(Num) -> receive {From, increment} -> From ! {self(), new_value, Num + 1} counter(Num + 1); end. 

Y de manera similar, tengo una clase de Ruby definida así:

 class Counter def initialize(num) @num = num end def increment @num += 1 end end 

El código de Erlang está escrito en un estilo funcional, utilizando la recursión de la cola para mantener el estado. Sin embargo, ¿cuál es el impacto significativo de esta diferencia? Para mis ojos ingenuos, las interfaces a estas dos cosas parecen muy similares: envías un mensaje, el estado se actualiza y recuperas una representación del nuevo estado.

La progtwigción funcional se describe tan a menudo como un paradigma totalmente diferente de la POO. Pero el actor de Erlang parece hacer exactamente lo que se supone que deben hacer los objetos: mantener el estado, encapsular y proporcionar una interfaz basada en mensajes.

En otras palabras, cuando paso mensajes entre actores de Erlang, ¿en qué se diferencia que cuando paso mensajes entre objetos de Ruby?

Sospecho que la dicotomía funcional / OOP tiene mayores consecuencias de las que estoy viendo. ¿Alguien puede señalarlos?

Dejemos de lado el hecho de que el actor de Erlang será progtwigdo por la máquina virtual y, por lo tanto, puede ejecutarse simultáneamente con otro código. Me doy cuenta de que esta es una gran diferencia entre las versiones de Erlang y Ruby, pero no es a eso a lo que me refiero. La concurrencia es posible en otros idiomas, incluyendo Ruby. Y aunque la concurrencia de Erlang puede tener un rendimiento muy diferente (a veces mejor), realmente no estoy preguntando sobre las diferencias de rendimiento.

Más bien, estoy más interesado en el lado funcional contra OOP de la pregunta.

En otras palabras, cuando paso mensajes entre actores de Erlang, ¿en qué se diferencia que cuando paso mensajes entre objetos de Ruby?

La diferencia es que en los lenguajes tradicionales como Ruby no se pasa ningún mensaje, pero la llamada al método se ejecuta en el mismo hilo y esto puede ocasionar problemas de sincronización si tiene una aplicación multiproceso. Todos los subprocesos tienen acceso a cada otra memoria de subprocesos

En Erlang, todos los actores son independientes y la única forma de cambiar el estado de otro actor es enviar un mensaje. Ningún proceso tiene acceso al estado interno de cualquier otro proceso.

En mi humilde opinión este no es el mejor ejemplo para FP vs OOP. Las diferencias generalmente se manifiestan al acceder / iterar y encadenar métodos / funciones en objetos. También, probablemente, entender qué es el “estado actual” funciona mejor en FP.

Aquí, pones dos tecnologías muy diferentes entre sí. Uno pasa a ser F, el otro OO.

La primera diferencia que puedo detectar de inmediato es el aislamiento de la memoria. Los mensajes se serializan en Erlang, por lo que es más fácil evitar las condiciones de la carrera.

Los segundos son los detalles de gestión de memoria. En Erlang, el manejo de mensajes se divide por debajo entre el remitente y el receptor. Hay dos conjuntos de lockings de estructura de proceso mantenidos por Erlang VM. Por lo tanto, mientras el remitente envía el mensaje, obtiene un locking que no está bloqueando las operaciones del proceso principal (se accede mediante el locking PRINCIPAL). En resumen, le da a Erlang una naturaleza en tiempo real más suave que un comportamiento totalmente aleatorio en el lado de Ruby.

Mirando desde afuera, los actores se parecen a los objetos. Encapsulan el estado y se comunican con el rest del mundo a través de mensajes para manipular ese estado.

Para ver cómo funciona la PF, debes mirar dentro de un actor y ver cómo muta el estado. Tu ejemplo donde el estado es un entero es demasiado simple. No tengo tiempo para dar un ejemplo completo, pero haré un bosquejo del código. Normalmente, un bucle de actor se parece a lo siguiente:

 loop(State) -> Message = receive ... end, NewState = f(State, Message), loop(NewState). 

La diferencia más importante de la POO es que no hay mutaciones variables, es decir, NewState se obtiene del Estado y puede compartir la mayoría de los datos con él, pero la variable Estado siempre permanece igual.

Esta es una buena propiedad, ya que nunca corrompemos el estado actual. La función f usualmente realizará una serie de transformación para convertir el estado en NewState. Y solo si / cuando tiene éxito, reemplazamos el estado anterior por el nuevo bucle de llamada (NewState). Así que el beneficio importante es la consistencia de nuestro estado.

El segundo beneficio que encontré es un código más limpio, pero lleva un tiempo acostumbrarse a él. Generalmente, ya que no puede modificar la variable, tendrá que dividir su código en muchas funciones muy pequeñas. Esto es realmente bueno, porque su código estará bien factorizado.

Finalmente, dado que no puede modificar una variable, es más fácil razonar sobre el código. Con los objetos mutables, nunca puede estar seguro de si alguna parte de su objeto se modificará y empeorará progresivamente si usa variables globales. No debes encontrarte con tales problemas al hacer FP.

Para probarlo, debe tratar de manipular algunos datos más complejos de una manera funcional mediante el uso de estructuras erlang puras (no actores, ets, mnesia o proc dict). Alternativamente, puedes intentarlo en Ruby con este

Erlang incluye el enfoque de paso de mensajes de OOP (Smalltalk) de Alan Kay y la progtwigción funcional de Lisp.

Lo que describe en su ejemplo es el enfoque del mensaje para la POO. Los procesos de Erlang enviando mensajes son un concepto similar a los objetos de Alan Kay que envían mensajes. Por cierto, puede recuperar este concepto implementado también en Scratch, donde los objetos en ejecución paralela envían mensajes entre ellos.

La progtwigción funcional es cómo se codifican los procesos. Por ejemplo, las variables en Erlang no pueden ser modificadas. Una vez que se han establecido, solo puedes leerlos. También tiene una estructura de datos de lista que funciona de manera muy similar a las listas de Lisp y se divierte, que son identificadas por la lambda de Lisp.

El mensaje que pasa en un lado y el funcional en el otro lado son dos cosas distintas en Erlang. Al codificar aplicaciones erlang de la vida real, pasa el 98% de su tiempo haciendo progtwigción funcional y el 2% pensando en el paso de mensajes, que se utiliza principalmente para la escalabilidad y la concurrencia. Para decirlo de otra manera, cuando llegue a un problema de progtwigción complejo, probablemente usará el lado FP de Erlang para implementar los detalles de algo, y use el paso de mensajes para escalabilidad, confiabilidad, etc.

Qué piensas de esto:

 thing(0) -> exit(this_is_the_end); thing(Val) when is_integer(Val) -> NewVal = receive {From,F,Arg} -> NV = F(Val,Arg), From ! {self(), new_value, NV}, NV; _ -> Val div 2 after 10000 max(Val-1,0) end, thing(NewVal). 

Cuando genera el proceso, vivirá por sí mismo, reduciendo su valor hasta que scope el valor 0 y envíe el mensaje {‘SALIR’, esto_ es_el_inicio} a cualquier proceso vinculado a él, a menos que se encargue de ejecutar algo como:

 ThingPid ! {self(),fun(X,_) -> X+1 end,[]}. % which will increment the counter 

o

 ThingPid ! {self(),fun(X,X) -> 0; (X,_) -> X end,10}. % which will do nothing, unless the internal value = 10 and in this case will go directly to 0 and exit 

En este caso, puede ver que el “objeto” vive su propia vida en paralelo con el rest de la aplicación, que puede interactuar con el exterior casi sin ningún código y que el exterior puede pedirle que haga cosas que usted no hizo. No sabes cuándo escribiste y comstackste el código.

Este es un código estúpido, pero hay algunos principios que se utilizan para implementar aplicaciones como la transacción mnesia, los comportamientos … En mi humilde opinión, el concepto es realmente diferente, pero hay que intentar pensar de manera diferente si quiere usarlo correctamente. Estoy bastante seguro de que es posible escribir código “OOPlike” en Erlang, pero será extremadamente difícil evitar la concurrencia: o), y al final no es una ventaja. Eche un vistazo al principio OTP que ofrece algunas pistas sobre la architecture de la aplicación en Erlang (árboles de supervisión, grupo de “1 servidores de un solo cliente”, procesos vinculados, procesos monitoreados y, por supuesto, asignación de patrón, mensajes, grupos de nodos … ).

    Intereting Posts