haskell - magmas - rocas metamorficas
¿Por qué FRP considera el tiempo como un factor para los valores? (3)
Behavior
difieren del Event
s principalmente en que un Behavior
tiene un valor en este momento, mientras que un Event
solo tiene un valor cada vez que aparece un nuevo evento.
Entonces, ¿qué queremos decir con "ahora mismo"? Técnicamente, todos los cambios se implementan como semánticas push o pull sobre las secuencias de eventos, por lo que solo es posible que signifiquemos "el valor más reciente a partir del último evento de consecuencia para este Behavior
". Pero ese es un concepto bastante peludo; en la práctica, "ahora" es mucho más simple.
El razonamiento de por qué "ahora" es más simple se reduce a la API. Aquí hay dos ejemplos de Reactive Banana.
Finalmente, un sistema FRP siempre debe producir algún tipo de cambio visible externamente. En Reactive Banana esto se ve facilitado por la función
reactimate :: Event (IO ()) -> Moment ()
que consume secuencias de eventos . No hay forma de que unBehavior
provoque cambios externos; siempre hay que hacer algo comoreactimate (someBehavior <@ sampleTickEvent)
para muestrear el comportamiento en momentos concretos.Los comportamientos son
Applicative
a diferencia delEvent
s. ¿Por qué? Bueno, supongamos queEvent
fue un aplicativo y pensamos en lo que sucede cuando tenemos dos flujos de eventosf
yx
y escribimosf <*> x
: dado que los eventos ocurren todos en diferentes momentos, las posibilidades de quef
yx
se definan simultáneamente son ( casi seguro ) 0. Entoncesf <*> x
siempre significaría la secuencia de eventos vacía que es inútil.Lo que realmente quiere es que
f <*> x
almacene en caché los valores más actuales para cada uno y tome su valor combinado "todo el tiempo". Ese es un concepto realmente confuso del que se puede hablar en términos de un flujo de eventos, así que, en cambio, consideramos quef
yx
toman valores para todos los puntos en el tiempo. Ahoraf <*> x
también se define como tomar valores para todos los puntos en el tiempo. Acabamos de inventarBehavior
s.
Los comportamientos se definen de manera ubicua como "valor variable en el tiempo" s 1 .
¿Por qué? siendo el tiempo la dependencia / parámetro para valores variables es muy poco común.
Mi intuición para FRP sería tener comportamientos como valores que varían en el evento; es mucho más común, mucho más simple, obtengo una idea mucho más eficiente y también lo suficientemente extensible para soportar el tiempo (evento tick).
Por ejemplo, si escribe un contador, no le importan las marcas de tiempo / hora asociadas, solo le importan los eventos "Aumentar botón pulsado" y "Disminuir botón pulsado".
Si escribes un juego y quieres un comportamiento de posición / fuerza, solo te preocupan los eventos de teclas WASD / flechas, etc. (a menos que prohíbes a tus jugadores moverse hacia la izquierda en la tarde, ¡qué inicuo!).
Entonces: ¿Por qué el tiempo es una consideración? ¿por qué marcas de tiempo? ¿Por qué algunas bibliotecas (por ejemplo reactive-banana
reactive
, reactive
) lo toman hasta el punto de tener valores Future
, Moment
? ¿Por qué trabajar con secuencias de eventos en lugar de simplemente responder a una ocurrencia de evento? Todo esto parece complicar demasiado una idea simple (valor variable en función del evento / evento); ¿Cuál es la ganancia? ¿Qué problema resolvemos aquí? (También me gustaría obtener un ejemplo concreto junto con una explicación maravillosa, si es posible).
1 Los comportamientos se han definido así que aquí , aquí , aquí ... y prácticamente en todas partes donde me he encontrado.
El papel FRP Push-Pull de Conal Elliott describe datos variables en el evento, donde los únicos puntos en el tiempo que son interesantes son cuando ocurren los eventos. Reactive
datos Reactive
que varían en el evento son el valor actual y el próximo Event
que lo cambiará. Un Event
es un punto Future
en los datos Reactive
varían en el evento.
data Reactive a = a ‘Stepper ‘ Event a
newtype Event a = Ev (Future (Reactive a))
El Future
no necesita tener un tiempo asociado, solo necesita representar la idea de un valor que aún no ha sucedido. En un lenguaje impuro con eventos, por ejemplo, un futuro puede ser un identificador de evento y un valor. Cuando ocurre el evento, estableces el valor y levantas el mango.
Reactive a
tiene un valor para a
en todos los puntos en el tiempo, entonces ¿por qué necesitaríamos Behavior
s? Hagamos un juego simple. En el medio cuando el usuario presiona las teclas WASD, el personaje, acelerado por la fuerza aplicada, aún se mueve en la pantalla. La posición del personaje en diferentes momentos es diferente, aunque no haya ocurrido ningún evento en el tiempo intermedio. Esto es lo que describe un Behavior
, algo que no solo tiene un valor en todos los puntos en el tiempo, sino que su valor puede ser diferente en todo momento, incluso sin eventos intermedios.
Una forma de describir Behavior
sería repetir lo que acabamos de mencionar. Behavior
son cosas que pueden cambiar los eventos intermedios. Los eventos intermedios son valores variables en el tiempo o funciones del tiempo.
type Behavior a = Reactive (Time -> a)
No necesitamos Behavior
, simplemente podríamos agregar eventos para marcas de reloj y escribir toda la lógica en todo el juego en términos de estos eventos de tic. Esto es indeseable para algunos desarrolladores ya que el código que declara cuál es nuestro juego ahora se entremezcla con el código que proporciona cómo se implementa. Behavior
permiten al desarrollador separar esta lógica entre la descripción del juego en términos de variables variables en el tiempo y la implementación del motor que ejecuta esa descripción.
Porque era la forma más sencilla en que podía pensar para dar una denotación precisa (significado independiente de la implementación) de la noción de comportamientos, incluidos los tipos de operaciones que deseaba, incluida la diferenciación e integración, así como el seguimiento de uno o más comportamientos ( comportamiento, pero no limitado a, generado por el usuario).
¿Por qué? siendo el tiempo la dependencia / parámetro para valores variables es muy poco común.
Sospecho que estás confundiendo la construcción (receta) de un comportamiento con su significado. Por ejemplo, un comportamiento puede construirse a través de una dependencia en algo como la entrada del usuario, posiblemente con una transformación sintética adicional. Entonces está la receta. El significado, sin embargo, es simplemente una función del tiempo, relacionada con la función de tiempo que es la entrada del usuario. Tenga en cuenta que por "función", me refiero en el sentido matemático de la palabra: un mapeo (determinista) de dominio (tiempo) a rango (valor), no en el sentido de que hay una descripción puramente programática.
He visto muchas preguntas preguntándome por qué el tiempo importa y por qué el tiempo continuo . Si aplicas la simple disciplina de dar un significado matemático en el estilo de la semántica denotacional (un estilo simple y familiar para los programadores funcionales), los problemas se vuelven mucho más claros.
Si realmente quiere asimilar la esencia y el pensamiento detrás de FRP, le recomiendo que lea mi respuesta a "Especificación para un lenguaje de programación funcional reactiva" y siga los indicadores, incluido "Qué es la programación reactiva funcional" .