jueves, 12 de agosto de 2010

Parábola del Actor y el Objeto

La idea de hacer este embrollo surge del artículo de Angel "Java" López Look, ma! No database!, pero es toda mi responsabilidad.

Supongamos que nos devolvimos en el tiempo en una pequeña tienda que vende un solo producto.

Hay un “desconta-dor” (viejo mañoso y malhumorado) que calcula el descuento de cada venta, agrega el valor al recibo y le pone un sello. A continuación el “vende-dor” envía el recibo al “autoriza-dor” por otro sello, y por último, este le envía una copia a el “bodegista-dor”. Así, el “bodegista-dor” descuenta la cantidad de artículos vendidos (y pone un sello) y los entrega (otro sello)... etc.

Supongamos que cada uno de estos señores “-dor” tiene twitter (o un mensajero) y emite sus acciones como alienado (una por vez). Supongamos también que hay un fan de los “-dor” llamado “conta-dor”, encargado de fiscalizar el dinero y los artículos vendidos, y que no pierde un mensaje.

Supongamos también que al “vende-dor” no le cae bien el “bodegista-dor” y husmea su canal llevando la cuenta de las ventas efectivamente entregadas (la comisión depende de ello). Y como la desconfianza es algo contagioso, el  “autoriza-dor” hace lo mismo.
Agreguemos varios “vende-dor” (para hacer esto ameno) con su propia identificación (id) y hagamos que sigan la rutina del primero.

Si esto fuera orientado a objetos, cada “compra-dor” creará sus propios “-dor” usando la taxonomía de la tienda (siendo él mismo un “-dor”). Estos clones de los “-dor” existirán mientras sean necesarios (ocupando un lugar en la memoria de la tienda) y serán desechados luego de registrar sus actos; excepto el “conta-dor” que existe sólo los jueves y se encarga de analizar todos los registros de la semana (la tarde completa) y luego entrega su informe para dejar de existir.

Antes de continuar, la notación Erlang para hablar de Actores:
    ->             Inicio de rutina (entonces).
    {}             Tupla
    []             Lista
    atom        Etiqueta (empieza en minúsculas.)
    Var          Variable (empieza en mayúsculas.)
    !               Send()
    self()   Devuelve mi identificador (algo parecido a un número).

Cualquiera entiende este mensaje:
  { quiero, YO, "Videojuego", "Nientiendo" }

Enviar mensaje a Papá:
  Papa ! { quiero, self(), "Videojuego", "Nientiendo" }

Donde “Papá” es un identificador de proceso (Pid).

El padre, que tiene varios hijos y no sabe cual de ellos manda el mensaje, responde con un mensaje genérico:
  Hijo ! { noHayDinero }

Uniendo ambos mensajes, el mailbox de Papá será:
receive
  { quiero, Hijo, Articulo, Marca } ->
     Hijo ! { noHayDinero }
end.

El resultado de self() se asigna a la variable Hijo mediante Pattern Matching. En Artículo queda "Videojuego" y en Marca queda "Nientiendo".


Volviendo a la tienda, el mensaje entre uno de los "vendedor" y el "descontador" será:
descontador ! { descontar, self(), Cantidad, Valor }

El descontador captura el Pid del vendedor en la variable Vendedor, hace un proceso y le devuelve otro mensaje:
receive
  {descontar, Vendedor, Cantidad, Valor } ->
      ValorConDescuento = Valor * (1 - PorcentajeDescuento),
      Vendedor ! {descuento, Cantidad, ValorConDescuento }
end.

También debe notificar al "contador" y seguir existiendo, razón por la cual se llama a sí mismo manteniendo su estado interno (el porcentaje de descuento para el artículo). El "descontador" sería:
descontaDor(PorcentajeDescuento) ->
  receive
     {descontar, Vendedor, Cantidad, Valor } ->
        ValorConDescuento = Valor * (1 - PorcentajeDescuento),
        Vendedor ! {descuento, Cantidad, ValorConDescuento },
        contador ! {descuento, Cantidad, ValorConDescuento },
        descontaDor(PorcentajeDescuento)
  end.

%% Vendedor es una variable, al igual que Cantidad, Valor y ValorConDescuento.
%% "contador" y "descuento" son etiquetas

Los "vendedor" también se comunican con el "autorizador" y el "bodegizador". Una idea simple del código para este actor:
vendeDor(PrecioArticulo, CompradorActual, VentasLogradas) ->
  receive
    {iniciarVenta, Comprador, Cantidad } ->
       %% Se omite la lógica para atender un comprador a la vez
       Valor = Cantidad * PrecioArticulo,
       descontador ! {descontar, self(), Cantidad, Valor},
       vendeDor(PrecioArticulo, Comprador, VentasLogradas);
    {descuento, Cantidad, Valor } ->
       autorizador ! {autorizar, Cantidad, Valor},
       vendeDor(PrecioArticulo, CompradorActual, VentasLogradas);
    {finVenta, _, _ } ->
       CompradorActual ! {retirarMercancia },
       vendeDor(PrecioArticulo, _, VentasLogradas + 1)
 end.
%%Donde “_” significa no importa o variable vacía.

El mensaje “iniciarVenta” llega de un “comprador” y el mensaje “finVenta” del “bodegador”.

Para “lanzar” estos procesos (algo como un constructor), se usa spawn (desovar), así:
   Pid_descontador = spawn(descontaDor, [0.01]).
   Pid_vendedor = spawn(vendeDor, [20.50, _, 0]).

Y como “descontador” solo hay uno, lo registramos con una identidad única (lo mismo con  autorizador, bodegador y contador):
   register(descontador, Pid_descontador).
   ...
   register(contador, Pid_contador).

Por esta razón, tanto el “vendedor” como el "descontador" no necesitaron los Pids para enviarles mensajes.

Luego de esta maraton, quedan de ejercicio mental los demás actores. La idea general, es que estos actores existen siempre en memoria, atendiendo y enviando mensajes sin necesidad de ser instanciados y destruidos cada vez, y ocupando muy poco espacio en memoria. Son concurrentes, asincrónicos e independientes, pues no comparten información de estado.

Volviendo al artículo de Angel que originó todo esto: ¿para qué la Base de Datos? Y me respondo, tal vez como un histórico, como un almacén de comprobantes (recibos y sellos de la tienda).


Para quién quiera explorar el origen (o entender de verdad): Erlang.

Sigue con: El acto termina.

Relacionados:
Erlang: Programación funcional y concurrente.
Mapeo de actores a objetos.