javascript - inmutable - mutabilidad js
Datos inmutables en sistemas asíncronos. (3)
En un intento de responder directamente a su pregunta:
"¿Cómo se ve el diseño de un sistema cuando hay varios orígenes de eventos asíncronos y queremos que todo sea inmutable? O al menos, ¿cuál es un buen patrón para controlar la mutabilidad en un sistema como este?"
El patrón de solución correcto para este diseño ha sido, en el mundo de Unix, las colas de mensajes FIFO asíncronos (AMQ), desde el Sistema 5 de todos modos, mientras que hay condiciones teóricas bajo las cuales las condiciones de carrera y la indeterminación del estado podrían ocurrir en la práctica, casi nunca ocurre. . De hecho, las primeras investigaciones sobre la confiabilidad de las AMQ determinaron que estos errores no se debieron al retraso de la transmisión, sino a la colisión con solicitudes de interrupciones síncronas, ya que las primeras AMQ eran esencialmente canalizaciones implementadas en el espacio del kernel. La solución moderna, de hecho, la solución Scala, es implementar la AMQ en la memoria protegida compartida, eliminando así las llamadas al kernel lentas y potencialmente peligrosas.
Resulta que si el ancho de banda total de su mensaje es menor que la capacidad total del canal y su distancia de transmisión es menor a un segundo de luz: resistencia / conmutación, su probabilidad de falla es cósmicamente baja (como algo del orden de 10 ^ -24 ). Hay todo tipo de razones teóricas por las que, pero sin una digresión hacia la física cuántica y la teoría de la información, aquí no se puede abordar de manera concisa, sin embargo, aún no se ha encontrado ninguna prueba matemática que demuestre de manera definitiva que ese es el caso, todo es estimaciones y práctica. Pero cada sabor de Unix se ha basado en esas estimaciones durante más de 30 años para una comunicación asíncrona confiable.
Si se está preguntando cómo se puede introducir el comportamiento de interrupción, el patrón de diseño es una cola de prioridad directa o secundaria, la adición de niveles de prioridad o orden agrega una pequeña sobrecarga a un manifiesto de mensaje y es cómo se pueden componer las llamadas síncronas y asíncronas.
El patrón de diseño para preservar el estado de inicio inmutable con múltiples instrucciones mutables es similar a los patrones de conservación de estado, puede usar colas de diferenciación o históricas. Una cola histórica almacena el estado original y una serie de cambios de estado, como un historial de deshacer. Mientras que una cola de diferenciación mantiene el estado inicial y la suma de todos los cambios (un poco más pequeños y más rápidos, pero no tan grandes en estos días).
Finalmente, si necesita lidiar con mensajes grandes o en paquetes que viajan a una gran distancia de una red enrevesada o dentro y fuera del kernel repetidamente, el patrón de diseño es agregar una dirección de origen para las devoluciones de llamada y una marca de tiempo, así como un pequeño manejo de corrección, Es por eso que TCP / IP, SMQ, Netbios, etc., todos incluyen estos en sus protocolos, por lo que, en caso de que sea necesario, modifique su cola de priorización / pedido para que sea consciente de los paquetes.
Me doy cuenta de que este es un tratamiento apresurado de un tema masivo, por lo que estoy contento de responderle si hay preguntas o puntos adicionales que deban aclararse.
Espero haber respondido tu pregunta y no me he desviado mucho de lo que estabas preguntando. :)
Publicado:
Estos son algunos buenos ejemplos de cómo y por qué usar este tipo de tipo de diseño de colas para aplicaciones concurrentes distribuidas, que se utilizaron para el corazón de la mayoría de las soluciones de diseño distribuido de FRP:
https://docs.oracle.com/cd/E19798-01/821-1841/bncfh/index.html
http://www.enterpriseintegrationpatterns.com/patterns/messaging/ComposedMessagingMSMQ.html
http://soapatterns.org/design_patterns/asynchronous_queuing
http://spin.atomicobject.com/2014/08/18/asynchronous-ios-reactivecocoa/
http://fsharpforfunandprofit.com/posts/concurrency-reactive/
y un video de Martin Odersky ...
https://www.typesafe.com/resources/video/martin-odersky---typesafe-reactive-platform
:)
Tengo una buena idea de los beneficios de usar datos inmutables en mis aplicaciones y me siento bastante cómodo con la idea de usar estas estructuras inmutables en un entorno de programación sincrónica simple.
Hay un buen ejemplo en algún lugar de Stack Overflow que describe el estado de administración de un juego al pasar el estado en una serie de llamadas recursivas, algo como esto:
function update(state) {
sleep(100)
return update({
ticks: state.ticks + 1,
player: player
})
}
Podemos realizar un trabajo gratuito arbitrario y sin efectos secundarios en el cuerpo de la función, luego devolvemos un nuevo estado, en lugar de mutar el anterior.
Parece bastante fácil traducir esto a un modelo asíncrono simple, por ejemplo, Javascript.
function update(state) {
const newState = {
player,
ticks: state.ticks + 1
};
setTimeout(update.bind(this, newState), 100);
}
Sin embargo, tan pronto como tengamos más fuentes para eventos asíncronos, parece ser mucho más difícil administrar mantener el estado inmutable y las funciones puras.
Si agregamos un evento de clic al ejemplo, terminamos con un código que se ve así.
window.addEventListener(''click'', function() {
// I have no idea what the state is
// because only our update loop knows about it
});
Ahora, obviamente, no quiero mutar el estado en este método, pero necesito acceder al estado para crear un nuevo estado, algo como esto.
window.addEventListener(''click'', function() {
const state = getState();
createState({
player,
clicks: clicks + 1
});
});
¿Pero parece que esto requiere algún tipo de administrador estatal mutable?
Alternativamente, supongo que podría agregar el evento de clic a una cola de acciones para ser procesadas dentro del ciclo de actualización, algo como:
window.addEventListener(''click'', function() {
createAction(''click'', e);
});
function update(state, actions) {
const newState = {
player,
ticks: state.ticks + 1,
clicks: state.clicks + actions.clicks.length
};
setTimeout(update.bind(this, newState, []), 100);
}
Una vez más, esto no parece particularmente funcional y se basa en al menos algún estado mutable en algún momento del camino. Estos son probablemente enfoques ingenuos que provienen de alguien que ha trabajado principalmente con un estado mutable y una imperativa programación orientada a objetos.
¿Cómo se ve el diseño de un sistema cuando hay varios orígenes de eventos asíncronos y queremos que todo sea inmutable? O al menos, ¿cuál es un buen patrón para controlar la mutabilidad en un sistema como este?
Quizás te interese echar un vistazo a Redux . Redux tiene un enfoque similar:
- Modela todo el estado de la aplicación como un único objeto inmutable.
- Las acciones del usuario son esencialmente mensajes que se envían a la
store
para un procesamiento arbitrario. - Las acciones son manejadas por las funciones del reductor en la forma
f(previousState, action) => newState
. Este es un enfoque más funcional que su versión original. - La
store
ejecuta los reductores y mantiene un único estado de aplicación inmutable.
Tienes razón en que esto no es estrictamente inmutable, ya que la tienda en sí tiene una referencia mutable al estado actual. Pero como otros han señalado, esto no parece ser un problema para la mayoría de las concepciones de datos inmutables.
Además de las acciones de la interfaz de usuario, es posible que también tenga una acción de tick
que se dispare en un bucle: es solo otro evento de entrada, procesado por el mismo conjunto de reductores.
Usando Object.freeze , puedes hacer que un objeto sea inmutable:
var o = {foo: "bar"};
Object.freeze(o);
o.abc = "xyz";
console.log(o);
Dará {foo: "bar"}
. Tenga en cuenta que el intento de establecer la nueva propiedad en la variable congelada fallará silenciosamente.
En este caso, después de crear el nuevo objeto de estado, congelarlo antes de llamar a rutinas de actualización o desencadenar eventos para evitar modificaciones posteriores.