Elm Creator Bars Dynamic FRP to Prevent Memory Leaks, Preserve Replay

"Controlling Time and Space: understanding the many formulations of FRP" by Evan Czaplicki — Strange Loop Conference

Strange Loop Conference

Pure functional languages cannot support dynamically reconfigurable signal graphs without risking unbounded memory leaks from historical event look-backs, making static architectures necessary for deterministic time-travel debugging.

AI-assisted summary of "Controlling Time and Space: understanding the many formulations of FRP" by Evan Czaplicki — original source. Check the original for full context.

Source published:

Key ideas

  • Evan Czaplicki distinguishes Elm's first-order FRP from higher-order FRP by its static signal graphs and infinite signals, which preserve temporal ordering and eliminate the need for historical event buffering. (6:17)
  • Higher-order FRP introduces dynamic graph reconfiguration via nested signals (`Signal (Signal a)`), but referential transparency forces an 'infinite look-back' dilemma where past events must be retained, risking severe space leaks. (15:30)
  • Asynchronous data flow libraries like Bacon.js and ReactiveX depart from classical FRP by abandoning static topologies, infinite lifespans, and synchrony in favor of finite streams and hot/cold subscription models. (20:53)
  • Arrowized FRP (AFRP) solves memory retention issues during dynamic switching by decoupling automatons from the external world, freezing state safely when nodes are unobserved. (23:30)

Detailed notes

## Background Published on YouTube by the Strange Loop Conference on September 21, 2014, this talk features Evan Czaplicki, the creator of Elm and developer at Prezi. The presentation outlines the core concepts of Functional Reactive Programming (FRP), surveys its historical and theoretical variants, and explains how Elm's specific formulation enables features like hot-swapping and time-travel debugging.

## TL;DR Czaplicki demystifies the broad umbrella of Functional Reactive Programming by contrasting Elm's static, first-order signal graphs with higher-order, arrowized, and asynchronous dataflow variants. He demonstrates how Elm's constraints—static topologies, infinite signals, and synchronous defaults—enable reliable Model-View-Update architectures, seamless code hot swapping, and scrubbable time-travel debugging without memory-leaking look-back issues. The presentation then walks through the fundamental trade-offs of dynamic switching in higher-order FRP, finite event streams in libraries like Rx and Bacon.js, and decoupled automatons in Arrowized FRP. Ultimately, Czaplicki provides a pragmatic taxonomy, urging developers to choose reactive paradigms based on concrete application needs, ordering guarantees, and domain constraints rather than academic prestige.

## Atmosphere & style The presentation carries an engaging, accessible, and pragmatic academic tone, delivered with self-deprecating humor and candid clarity. Czaplicki speaks as a battle-tested functional programmer eager to spare his audience the years of conceptual confusion he endured, weaving effortlessly between high-level architectural philosophy and live visual demonstrations. Watching him casually scrub Mario backward across frame-by-frame keystrokes or laugh off accidentally creating 'Mega Mario' gives the session an exciting, workbench feel. It feels like an illuminating insider briefing that cuts through dense functional programming jargon to champion practical, understandable engineering.

## Bulletin - [06:17](https://www.youtube.com/watch?v=Agu6jipKfYw&t=377s) Evan Czaplicki distinguishes Elm's first-order FRP from higher-order FRP by its static signal graphs and infinite signals, which preserve temporal ordering and eliminate the need for historical event buffering. - [15:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=930s) Higher-order FRP introduces dynamic graph reconfiguration via nested signals (`Signal (Signal a)`), but referential transparency forces an 'infinite look-back' dilemma where past events must be retained, risking severe space leaks. - [20:53](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1253s) Asynchronous data flow libraries like Bacon.js and ReactiveX depart from classical FRP by abandoning static topologies, infinite lifespans, and synchrony in favor of finite streams and hot/cold subscription models. - [23:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1410s) Arrowized FRP (AFRP) solves memory retention issues during dynamic switching by decoupling automatons from the external world, freezing state safely when nodes are unobserved.

## Key takeaways - [00:01](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1s) The term 'FRP' spans a diverse set of distinct paradigms, requiring explicit taxonomy across graph dynamism, signal lifetimes, and synchrony defaults. - [01:11](https://www.youtube.com/watch?v=Agu6jipKfYw&t=71s) First-order FRP in Elm uses four primitive operations to structure events without chaotic callbacks: input signals, `lift`, `foldp`, and signal combination (`merge`/`lift2`). - [06:17](https://www.youtube.com/watch?v=Agu6jipKfYw&t=377s) Elm's core design relies on four constraints: signals are tied to the physical world, signals are infinite, graphs are static, and event processing is synchronous by default. - [06:17](https://www.youtube.com/watch?v=Agu6jipKfYw&t=377s) The Model-View-Update pattern (the Elm Architecture) is a direct structural consequence of maintaining a static, event-driven signal graph. - [10:22](https://www.youtube.com/watch?v=Agu6jipKfYw&t=622s) A static signal graph enables reliable runtime code hot-swapping and deterministic time-travel debugging by replaying recorded input events over updated state step functions. - [13:00](https://www.youtube.com/watch?v=Agu6jipKfYw&t=780s) Higher-order FRP enables dynamic signal graphs using a `join` operation on signals of signals, but managing unobserved branches introduces substantial operational complexity. - [15:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=930s) Under equational reasoning and referential transparency, dynamic signal switching requires reconstructing cumulative history, causing an unbounded space leak without linear types or specialized type systems. - [20:53](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1253s) Imperative reactive libraries (e.g., RxJS, Bacon.js) sidestep space leaks by discarding purity and structural sharing, instantiating fresh finite streams rather than tracking past identity. - [23:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1410s) Arrowized FRP models computations as disconnected input-output state machines ('automatons'), enabling dynamic composition without continuous background churn or memory leaks. - [23:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1410s) Because arrowized automata cannot read external hardware directly, AFRP serves primarily as a pure computation structuring tool rather than a comprehensive system I/O runtime. - [31:29](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1889s) Architectural selection should be driven by deployment realities: asynchronous streams fit multi-client servers, whereas synchronous static graphs suit deterministic client-side UI tooling. - [34:01](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2041s) Treating signals as effectful directly shifts an architecture out of pure FRP and into imperative asynchronous stream processing. - [36:39](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2199s) UI component modularity in Elm is effectively achieved via hierarchical nesting of pure Model-Update-View functions and standard modules rather than complex dynamic reactive graphs. - [38:36](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2316s) The push versus pull distinction in reactive UI is largely an implementation detail, as boundary-triggered push updates functionally mirror on-demand pull evaluation. - [38:36](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2316s) Elm prioritizes human-driven graphical user interfaces, making complex backpressure and rate-limiting protocols unnecessary for its core design.

## Detailed walkthrough ### [00:01](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1s) Demystifying Functional Reactive Programming

Evan Czaplicki, the creator of the Elm programming language, opens by stating that his talk is the distillation of years spent wrestling with Functional Reactive Programming (FRP) down into a single 40-minute session. His motivation is to spare others from having to endure that same painful journey of confusion. He jokes about the title, "Controlling Time and Space," admitting that at the time he submitted it, he felt like a Time Lord due to his work on time-travel debugging. However, the true core purpose of the talk is to dissect and categorize the wide diversity of concepts that travel under the umbrella label of "FRP."

To structure this exploration, Czaplicki establishes three overarching goals for the audience: 1. Understand what FRP actually is. 2. Categorize the diverse tools and paradigms that fall under that label. 3. Evaluate those variations systematically to understand their real trade-offs.

He outlines the roadmap: he will first introduce what he calls "first-order FRP"—which represents how Elm structures reactive systems—and then branch outward into other variations that build upon or alter this foundational model.

```mermaid flowchart LR accTitle: First-Order FRP Signal Graph Pipeline accDescr: Pipeline showing external user interactions transforming through a signal graph and outputting actions to the world.

subgraph Inputs["Inputs (World)"] A["User Events<br/>(Mouse, Keys)"] end

subgraph Graph["Signal Processing"] B["Signal Graph<br/>(Transform, Fold, Merge)"] end

subgraph Outputs["Outputs (World)"] C["Screen Render /<br/>HTTP Requests"] end

A --> B B --> C ```

[01:11](https://www.youtube.com/watch?v=Agu6jipKfYw&t=71s) The Mechanics of First-Order FRP in Elm

Czaplicki turns to the operational mechanics of first-order FRP, which mirrors the architecture of Elm. At its heart, the system is designed to handle interactions from the external world. A user sits at a computer typing and looking at the screen, producing mouse events and keystrokes. In typical JavaScript, this input lands in a chaotic tangle of ad-hoc callbacks. In Elm, inputs enter an explicitly structured processing network called a signal graph. As Czaplicki explains the motivation: “so sort of what FRP is about is rather than using callbacks and these kind of ad hoc ways of structuring time um we actually have a system that that gives it explicit structure that you can talk about in a in a more concise way”. The processed values are then communicated back to the platform to update the screen display or dispatch HTTP requests.

The signal graph is composed of four fundamental building blocks:

- **Inputs from the World:** Raw external inputs are modeled as signals. For example, `Mouse.position` does not have the type of a plain integer pair, but rather `Signal (Int, Int)`—a pair of integers changing over time. Similarly, `Keyboard.lastPress` tracks the most recent character pressed. Czaplicki humorously illustrates this with a trace of his typing: typing out `"hello strange Loop"`, then pausing to surf the web after getting distracted, and finally resuming typing later, showing how values evolve along a continuous timeline. - **Transformations (`lift`):** Raw signals can be modified using `lift`, which takes a pure mapping function `(a -> b)` and applies it across a `Signal a` to yield a `Signal b`. For instance, applying an `isConsonant` predicate across a keystroke signal converts the stream of characters (`'h'`, `'e'`, `'l'`, `'l'`, `'o'`) into a derived signal of booleans (`True`, `False`, `True`,...). - **Accumulating State (`foldp`):** State accumulation is handled by `foldp`, which stands for "fold from the past" (designed as the temporal analog to list operations `foldl` and `foldr`). Given an initial baseline state and an accumulator function, `foldp` updates the state on every incoming event. Czaplicki demonstrates counting keystrokes by feeding the key-press signal into `foldp`, stepping an integer counter forward (`1, 2, 3, 4, 5...`), noting that this same mechanism scales up to manage full application state, such as a complete Todo list. - **Combining Signals (`merge` and `lift2`..`liftN`):** Multiple streams can be unified using `merge` (which combines two signals of the same type) or combined via `lift2`, which applies a two-argument function across two concurrent signals whenever either updates (e.g., pairing discrete X and Y coordinate signals). Elm provides `lift` variants up to `lift8`, beyond which developers can combine higher-arity signals using standard functional patterns.

[06:17](https://www.youtube.com/watch?v=Agu6jipKfYw&t=377s) Core Design Principles and Architectural Payoffs

Moving beyond raw operational syntax, Czaplicki highlights the theoretical assumptions and constraints that define Elm's first-order FRP design:

- **Connected to the world:** Signal inputs directly represent concrete hardware or environmental realities like mouse coordinates and keyboard input. - **Infinite signals:** Signals cannot be arbitrarily created or destroyed at runtime. Czaplicki likens deleting an input signal to physically ripping the trackpad or mouse off a laptop mid-execution: inputs are fixed constants of the operating environment. - **Static signal graphs:** The topological shape of the signal network is fixed and fully known from the moment of application startup. Czaplicki notes that while dynamic topology sounds tempting, static topology yields massive guarantees and implementation benefits. - **Synchronous by default:** Branching paths in the graph must preserve temporal ordering. If a user types `"hello"`, the processing paths must preserve that sequencing through all intermediate downstream nodes without interleaving anomalies.

These design constraints directly enable two major benefits: high computational efficiency and a clean architectural separation. Efficiency is preserved because the system is purely event-driven; stateful nodes only need to inspect their single immediately preceding state value rather than maintaining historical buffers.

On the architectural front, the static graph structure directly gives birth to what is known as the Elm Architecture (Model-View-Update). Demonstrating a TodoMVC application integrated with browser `localStorage`, Czaplicki reveals the four clean structural parts of any standard Elm program:

1. **Model:** The explicit type definition of the entire application state (e.g., a list of tasks with description strings, editing flags, and completion states). 2. **Update:** A pure function stepping the model forward in response to user actions (such as adding or deleting tasks). 3. **View:** A pure, declarative transformation converting the current model into rendered HTML virtual DOM nodes, allowing developers to compose UI via standard functions like `List.map`. 4. **Inputs/Signals:** The pipeline feeding external user events into the update loop.

Czaplicki emphasizes that this clean separation is not an accidental style guide, but the direct consequence of the underlying FRP primitive choices: “that architecture that structure comes from the static signal graphs they guide you towards writing your applications that way”.

[10:22](https://www.youtube.com/watch?v=Agu6jipKfYw&t=622s) Hot Swapping and Time-Traveling in Elm

Evan Czaplicki demonstrates that beyond clean architecture, the major payoff of having a static signal graph is the ability to hot-swap code at runtime and travel through time while debugging. Demonstrating using the Mario program from the previous year, Czaplicki shows Mario standing on the screen. He adjusts the gravity constant in the source code, jumps Mario, and then changes gravity again to make it noticeably stronger. Because the signal graph is static and well-defined, these changes propagate straight into the running application without losing the current game state: “so we're able to change the behavior of the program while the program is running and sort of see those changes propagate uh automatically”.

```mermaid flowchart LR accTitle: Hot Swapping and Time Travel Debugger Workflow accDescr: Diagram showing how code edits in Sublime Text update the running Elm app and how event history allows scrubbable replays. subgraph Inputs ["Development & Events"] direction TB E["Editor (Sublime Text)"] H["User Inputs / Arrow Keys"] end subgraph Runtime ["Elm Static Signal Graph"] direction TB S["State (Model)"] U["Step Function (Update)"] D["Render (Display)"] end subgraph Debugger ["Time Travel Tooling"] direction TB T["Event History Log"] R["Scrubber / Slider"] end

E -->|"hot swap code"| U H -->|"record event"| T T -->|"replay events"| S R -->|"scrub to tick t"| S S --> U U --> D ```

Building on hot swapping, Czaplicki demonstrates a new version of the Elm Time Traveling Debugger, which he notes is about to be released in an easy-to-install package. Loading up the Mario game, he jokes about an unintended visual: "and we've got like Mega Mario ah man I changed the code by accident last night and forgot to change it back". With the debugger running, he pauses the program and scrubs the timeline backward toward the beginning. Instead of walking Mario left as he had done originally, he resumes playback and walks Mario to the right.

The debugger exposes the concrete stream of data driving the program at every tick: as Czaplicki steps backward frame by frame, the viewer can see the arrow keys being pressed (`right`, then `up-right`), and then released. Furthermore, hot swapping integrates directly with time travel: when Czaplicki opens Sublime Text, alters the gravity setting, and hits save, the updated jump logic re-runs across the entire recorded timeline of inputs. Mario suddenly leaps off the top of the screen because the accumulated jump velocity recalculates under the new rules, prompting Czaplicki to dial it down so Super Mario can have a reasonable jump height again.

- Hot swapping allows developers to adjust values such as physics constants while the program stays live and maintains its current state. - The Time Traveling Debugger captures the discrete history of external input events (like keyboard arrows), allowing scrub-and-replay from arbitrary points in time. - Changes made in an external editor like Sublime Text propagate not just into future frames, but across the entire logged history retroactively.

---

[13:00](https://www.youtube.com/watch?v=Agu6jipKfYw&t=780s) The Promise and Challenge of Higher-Order FRP

Czaplicki explains the fundamental architectural invariant that enables these developer tools: “a lot of these abilities to swap and code and Replay code comes from the fact that we know what if we know what the program looks like at the start we know what it's going to look like in an hour in a day um and we don't have to do some kind of crazy complex analysis to to figure that out or to match things up.” This stability is the defining characteristic of first-order Functional Reactive Programming (FRP).

However, functional programmers naturally ask what happens if signal graphs are allowed to reconfigure themselves dynamically at runtime. The mindset is often: "what is higher order FRP surely higher is better". As Czaplicki points out, exploring dynamic signal graphs has been a central research question in the field for years because it turns out to be surprisingly and deceptively difficult. He emphasizes that switching paradigms is never an objective upgrade; it is always an engineering trade-off where acquiring new expressive power requires giving up other properties.

In higher-order FRP, dynamic reconfiguration is traditionally achieved by introducing a `join` function that operates on a nested signal of signals (`Signal (Signal a)`). Conceptually, this can be visualized as having multiple incoming signals and using an outer signal to dynamically choose which inner signal to listen to at any given moment. But the moment dynamic switching is introduced, hard operational questions immediately emerge: What happens to the signals that are currently not being listened to? Should they keep running in the background, or should they be paused? If unobserved signals are introduced and accumulate over time, managing their state and lifecycle becomes significantly more complicated.

---

[15:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=930s) The Memory Problem: Infinite Look-Back and Equational Reasoning

To expose why higher-order FRP is so difficult, Czaplicki walks through a concrete example involving a mouse-click counter:

```mermaid flowchart TD accTitle: Higher Order FRP Join Dilemma accDescr: Flow diagram showing how switching between a click counter and a constant zero leads to equational reasoning requiring past event replay. subgraph StreamSwitch ["Selector Signal: Bool"] B{"Condition"} end subgraph Branches ["Signal Expressions"] C1["count Mouse.clicks"] C2["constant 0"] end subgraph Evaluation ["Join Evaluation"] J["join (Signal of Signals)"] Out["Current Count Output"] end

B -->|"True"| C1 B -->|"False"| C2 C1 --> J C2 --> J J --> Out ```

Consider a signal that counts mouse clicks, and another signal expression that switches between counting clicks and returning a constant zero based on a boolean value: - If the selector signal is `True`, it yields the click count. - If the selector signal is `False`, it yields `0`.

Czaplicki poses a timeline riddle: suppose the program begins with `True` and the user clicks 5 times, raising the counter to 5. The boolean signal is switched to `False`, resetting the output to 0. While it is set to `False`, the user clicks 5 more times. Finally, the selector is set back to `True`. What should the signal evaluate to? 1. Should it be `0` (restarting from scratch)? 2. Should it be `5` (resuming from where it left off)? 3. Should it be `10` (the cumulative count of all clicks over the application's entire run)?

Because this research originated in purely functional languages designed around equational reasoning and referential transparency, the choice is forced: identical expressions must behave identically and be substitutable. If `count Mouse.clicks` appears in the program, replacing it with a shared reference cannot alter its semantic meaning. Therefore, when the program switches back to `True`, the count must reflect the total history of clicks—in this case, 10.

This semantic guarantee leads directly to an operational disaster. If a user starts an application where a signal evaluates to `False` (emitting 0) for 20 minutes while clicking constantly, and then suddenly switches to an expression `count Mouse.clicks` that had never previously been evaluated, the runtime must still produce the full cumulative total. As Czaplicki highlights: “creating a new signal may need infinite look back I may need to look back at all the previous events to be able to reconstruct the signal so that I have these properties that I want”.

The consequence is a severe space leak: memory usage grows linearly with time because the system is forced to retain every historical event just in case an uninstantiated signal later needs to reconstruct its state from the beginning of time.

---

[18:01](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1081s) Bounded Space Solutions and Architectural Realities

The community's response to this dilemma has not been to discard dynamic switching, but rather to use sophisticated type systems to rule out infinite look-back. Czaplicki points out that modern Haskell higher-order FRP libraries rely on clever type-level tricks to guarantee bounded memory usage.

A prime example is the academic paper titled *"Higher-Order Functional Reactive Programming in Bounded Space"*, on which Czaplicki quips: “the names get very long for these papers when your topic is functional reactor programming”. In that paper, the author uses linear types—a specialized type system virtually absent from mainstream functional languages like Haskell, OCaml, or Agda, with Idris only recently experimenting with similar concepts. By enforcing linear usage guarantees, the type system prevents the programmer from writing any signal transformation that demands retroactively looking back into historical events without an explicit programmer instruction to retain that data.

However, resolving the problem via complex types brings significant tradeoffs: - Signal graphs lose their static structure, making hot swapping and time-travel debugging substantially harder or impossible to implement cleanly. - The clean architectural patterns of simple FRP give way to complicated APIs and intimidating type machinery. - Even if the technical challenges were completely solved, the architectural value is dubious. Czaplicki revisits the TodoMVC application: if signals could be dynamically switched inside the state model, tasks might be represented as `List (Signal Task)` rather than a plain data `List Task`. Czaplicki expresses skepticism that managing collections of nested signals provides a cleaner design than updating a flat, immutable data structure, noting: “I'm not sure that's a better way um I think that's something that uh I'd like to see some examples and that could be that could be quite interesting but I'm not I'm not totally sold yet”.

Czaplicki then turns to the next major variant of reactive programming: asynchronous dataflow, exemplified by Reactive Extensions (Rx) and ReactiveCocoa.

[20:53](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1253s) Asynchronous Data Flow in Imperative Languages: Bacon.js and Reactive Extensions

When reactive concepts appear in imperative environments—such as JavaScript libraries like Bacon.js or ReactiveX (RxJS)—they represent a distinct departure from classical functional reactive programming. Evan Czaplicki points out that whenever you encounter something marketed as FRP in an imperative language, it almost certainly belongs to this asynchronous data-flow category. In contrast to strict, pure formulations, these libraries discard several foundational constraints from the original design checklist. The assumption that signal graphs are static is immediately eliminated, along with the assumption that signals are infinite. In this paradigm, signals act as finite event streams: a stream can spin up, emit four events, and terminate. Furthermore, synchronous execution by default is abandoned in favor of asynchronous dispatch. While developers can still orchestrate synchrony manually, asynchrony is baked in as the default operating model.

```mermaid flowchart LR accTitle: Imperative Signal Lifecycle and Cold Versus Hot Streams accDescr: Demonstrates how imperative reactive streams are instantiated anew, execute asynchronously, and behave based on hot or cold subscriptions. subgraph S1["Stream Creation"] A["Event Source"] --> B["New Signal Instance"] end subgraph S2["Evaluation Modes"] B --> C{"Subscriber Attached?"} C -->|"Hot Signal"| D["Emits Events Continuously"] C -->|"Cold Signal"| E["Stays Dormant Until Listened To"] end subgraph S3["Disposal"] D --> F["Terminates / GC Cleans Up"] E --> F end ```

These systems handle higher-order streams using an operation commonly called `flatten`, which merges streams of streams. Czaplicki observes with a grin that imperative languages benefit from naming things intuitively for everyday programmers. However, because purity and referential transparency are not enforced, behavioral semantics change drastically. In an earlier pure example, defining an expression like `count mouseClicks` assumed consistent identity across references. In an imperative runtime, no such equivalence exists. Czaplicki explains the practical reality: “Now we're in an imperative language and no one ever said you were going to have that guarantee so whenever you create one of these signals you're creating a totally new one.” If a toggle alternates between counting clicks and returning zero, switching to false resets the count; switching back to true instantiates an entirely fresh signal starting from zero, while runtime garbage collection cleans up unreferenced nodes. This design introduces the puzzle of unobserved signals, leading libraries like Reactive Extensions to divide streams into "hot" and "cold" variants: a hot signal continues pushing values regardless of whether anyone is listening, whereas a cold signal halts until an observer subscribes.

- Static signal graphs are crossed off; graphs are dynamically constructed and torn down at runtime. - Infinite signals are replaced by finite event streams that complete or die after emitting values. - Asynchrony replaces synchronous execution as the system default. - Structural sharing is abandoned: instantiating a stream produces a distinct runtime instance without referential equivalence. - Reactive Extensions introduces hot signals (which run and produce events unprompted) and cold signals (which wait for active subscribers).

---

[23:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1410s) Arrowized FRP: Decoupling Computations from the World with Automatons

Arrowized Functional Reactive Programming (AFRP) emerged in Haskell with libraries such as Yampa and later Netwire, while Elm explored the same paradigm via its `Automaton` library. Like higher-order FRP, arrowized FRP eliminates the constraint that signal graphs must be static. However, it introduces a radically different philosophical trade-off: it crosses off the rule that signal graphs must be directly connected to the outside world. Instead of dealing with ambient, globally wired signals, arrowized FRP models stateful computation through isolated step functions known as automatons. Czaplicki describes an automaton metaphorically as a tiny self-contained robot that accepts an input on one side and produces a transformed output on the other side.

```mermaid flowchart TD accTitle: Automaton Composition and State Retention accDescr: Shows pure and stateful automatons connected in sequence, with dormant automatons safely preserving state when switched out of the active data path. subgraph Active["Active Execution Path"] IN["Input Event"] --> P1["Pure Automaton (+1)"] P1 --> P2["Pure Automaton (+1)"] P2 --> S1["Active Stateful Automaton (Count: 5)"] S1 --> OUT["Output Result"] end subgraph Inactive["Dormant / Disconnected Automatons"] S2["Green Automaton (Count: 3)"] S3["Blue Automaton (Count: 0)"] end ```

The basic API in Elm's `Automaton` library illustrates this cleanly: - `pure`: Wraps a pure function into an automaton (for instance, an incrementer where passing 1 outputs 2, 42 yields 43, and 100 returns 101). - `state`: Embeds persistent internal state similar to `foldp`. An automaton initialized with a base state of 0 updates its internal accumulator upon receiving incoming inputs (e.g., tallying strings like `"hello"` and `"how are you"` into sequential counts). - Chaining (`>>>`): Connects two automatons end-to-end, feeding the output of one into the input of the next, such as piping two `+1` stages together to form a `+2` pipeline.

The brilliance of this architecture becomes obvious during dynamic switching. Czaplicki walks through a scenario with three separate counting automatons initialized to zero. When the first automaton is placed in the active position, it processes five events and its state reaches five. It is then switched out for a second automaton, which processes three events. Because the disconnected nodes are completely severed from the external world, they freeze their internal state without leaking memory or continuing to churn cycles. Czaplicki highlights this core advantage: “What's really clever here is that these nodes aren't connected to the world so when we take it out of the graph this question of what happens does it keep running it just answered uh it doesn't keep running it's not connected to anything.” The severe drawback, however, is that an automaton cannot directly reference external hardware events like mouse coordinates; values must be manually sampled and fed into it. Consequently, arrowized FRP functions as a code structuring tool—competing directly with pure functions and module systems—rather than a comprehensive I/O runtime, though it has found notable success in domain-specific tasks such as audio processing and game loops.

---

[27:59](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1679s) Mapping the Design Space: Static Graphs, Finite Streams, and Pragmatic Architecture

Synthesizing these competing formulations, Czaplicki emphasizes that different FRP implementations are not in hostile competition; rather, they occupy complementary niches within a broader design landscape. In his overview diagram, Elm is positioned at the intersection of these styles because it strategically adopts features from each. Higher-order dynamic FRP adds capabilities, but Czaplicki deliberately avoids introducing it into Elm's core to preserve Elm's strong static guarantees—though he remains open to it if someone designs a formulation that retains those guarantees. Conversely, asynchronous data flow libraries like Bacon.js or Rx do not belong inside Elm; instead, developers should embed Elm inside them. At Prezi (Czaplicki's employer at the time), engineers can write a dedicated interactive UI component in Elm with full compiler guarantees and expose it outwards to integrate into an enclosing Bacon.js or Rx application architecture. Meanwhile, arrowized FRP requires no runtime compromises at all, living comfortably as a userland Elm library.

| FRP Formulation | Signal Graph Topology | Signal Lifespan / Nature | Relationship to External World / Elm | Primary Ecosystem Examples | |:--- |:--- |:--- |:--- |:--- | | **First-Order FRP (Elm Core)** | Static (fixed at compile time) | Infinite signals | Synchronous by default, globally wired to events | Elm | | **Higher-Order FRP** | Dynamic (graphs alter at runtime) | Infinite signals | Synchronously evaluates nested signals | Classical Haskell FRP papers | | **Asynchronous Data Flow** | Dynamic | Finite event streams (can end) | Imperative, asynchronous; wraps Elm externally | Bacon.js, ReactiveX (RxJS) | | **Arrowized FRP** | Dynamic switching allowed | Explicit signals omitted | Disconnected from the world; functions as an internal library | Yampa, Netwire, Elm `Automaton` |

To navigate this taxonomy, Czaplicki points developers to two fundamental architectural questions. First, does the system distinguish between static graphs (which remain rigid and analytically tractable) and dynamic graphs (which mutate at runtime)? Second, within dynamic graphs, how are signals modeled: are they infinite (common in Haskell), finite streams (common in imperative environments), or omitted entirely via decoupled arrows?

As a warning chime plays—prompting him to joke that he is running low on time—Czaplicki underscores the practical questions practitioners must ask when choosing an event architecture. Developers must verify whether a library is synchronous by default so input ordering is preserved without race conditions. Czaplicki emphasizes this guarantee: “When it comes to categorizing the questions you want to be asking are is it synchronous by default right am I getting these ordering guarantees where I know that if someone types hello and clicks a button that we're going to get it in that order and not the button click happens halfway through cuz the processing happens too takes too long or something.” Finally, systems must permit controlled asynchrony when necessary: while synchrony is the ideal default for local state, tasks like HTTP requests and disk writes must run asynchronously, a balance that Elm deliberately bakes into its foundational design.

- Detailed formal history and literature citations covering first-order through higher-order FRP can be found in Evan Czaplicki's master's thesis (*Concurrent FRP for GUIs*). - Architectural selection hinges on whether an application requires strict ordering guarantees (synchronous default) or background side-effect delegation (asynchronous escape hatches).

[31:29](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1889s) Evaluating FRP Formulations by Application Needs

When assessing different Functional Reactive Programming formulations, Evan Czaplicki emphasizes that not all existing systems have an explicit or well-defined plan for how asynchrony should behave—a critical detail developers must scrutinize, particularly when exploring higher-order FRP. Programmers must ask concrete architectural questions: can the system talk directly about inputs? Is it structured as an arrowized system, or is it primarily a mechanism for routing events? Can the underlying signal graph be reconfigured dynamically at runtime? Czaplicki cautions against choosing a technology based merely on impressive academic vocabulary or library marketing, arguing instead that practical project requirements must dictate the choice.

Asynchronous data flow systems like Reactive Extensions (Rx) shine on the server, where ten users might connect simultaneously requesting data; having asynchronous behavior by default prevents any single client from blocking others based on arrival order. Conversely, if an application requires global synchrony or relies on the deterministic, replayable time-travel debugging tools Elm provides, an asynchronous-by-default architecture is a poor fit. Czaplicki points out that debugging dynamic graph implementations is notoriously elusive, warning developers that choosing a paradigm is a long-term commitment: “this isn't just a cool thing to start with you have to carry this through for the whole life of your application and finally does the code come out nice”. While dynamic and asynchronous models offer broader feature sets, they introduce significant cognitive overhead, making it crucial to weigh whether added complexity is truly justified over simpler alternatives. Czaplicki closes his presentation by suggesting that mastering the foundational concepts in Elm makes it significantly easier to navigate other FRP paradigms across the broader programming landscape.

- **Asynchrony design**: Not all higher-order FRP libraries provide a coherent model for asynchronous execution. - **Server vs. Client trade-offs**: Rx-style asynchronous data flow excels when handling concurrent client connections on a server, whereas Elm's synchronous signal graph enables deterministic time-travel debugging. - **Complexity cost**: Dynamic graph reconfiguration provides power at the expense of difficult debugging and long-term code maintainability.

---

[34:01](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2041s) Q&A: Referential Transparency, Effectful Signals, and Automata

Opening the audience Q&A, an attendee asks about the `join` operator in higher-order FRP and the challenge of referential transparency. The attendee wonders why systems do not simply treat signals as inherently effectful rather than attempting to look backward in time across all past events to satisfy purity constraints: “is there a reason you don't just model signals as effectful then done with it”. Czaplicki explains that treating signals as effectful moves the system directly into the realm of asynchronous data flow libraries like Reactive Extensions or Bacon.js. While higher-order FRP libraries historically enforced the invariant that identical signal definitions should evaluate to identical historical values, relinquishing that constraint is a deliberate design trade-off that abandons referential transparency in exchange for pragmatic event streaming.

```mermaid flowchart LR accTitle: Stepping an Automaton in Arrowized FRP accDescr: Diagram showing how an input value and current automaton produce an output value and a new automaton instance. subgraph Step["Automaton Step Execution"] In["Input Value"] AutoCurr["Automaton (State N)"] In --> AutoCurr AutoCurr --> Out["Output Value"] AutoCurr --> AutoNext["Next Automaton (State N+1)"] end ```

Another audience member asks about arrowized FRP and how dynamic signal switching behaves once signals are wired up in a running Elm application. Czaplicki explains that in an arrowized FRP model, dynamism is built directly into the structure of automata before they are connected to runtime inputs. Stepping an automaton does not mutate an in-place graph; rather, it pure-functionally transitions the state forward: “so when you step in aaton forward what you're really getting is it the value it produces and the new automaton that you should run”. Czaplicki likens this process to forking reality: the caller receives both the current output value and a brand-new automaton configured to process future values, preserving past history without unexpected runtime coupling.

- **Effectful signals**: Treating streams as side-effecting is the core distinction separating FRP from libraries like Bacon.js and Rx. - **Automata stepping**: Arrowized systems evaluate by taking an input and returning a pair consisting of the output value and the updated automaton. - **Dynamic switching**: Arrowized automata encapsulate state transitions functionally rather than relying on global dynamic graph mutations.

---

[36:39](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2199s) Q&A: Component Modularity and the Nested Elm Architecture

An attendee asks about achieving component modularity within an Elm program, using the canonical TodoMVC application as an example: how would one extract the single-line task editor component and reuse it across another application? Czaplicki acknowledges that while higher-order or arrowized FRP can provide component abstraction by wrapping dynamic subgraphs, his current advocacy in Elm centers on standard functional building blocks: “What I my opinion is that this is something we should Explore More in Elm what I'm advoc right now... is sticking with module system and functions”.

Instead of relying on complex reactive abstractions, Czaplicki recommends structuring applications hierarchically using modules that expose a Model, an update function, and a view function. To modularize individual tasks, a developer writes a self-contained task module defining its model, update logic, and view rendering. The higher-level to-do list module then embeds the task's model, update, and view within its own architecture. Czaplicki admits that while higher-order reactive components remain an open area of inquiry, the functional module-nesting pattern offers clarity, noting that “it's not clear to me that one way is going to be superior to the other and I think there hasn't been enough exploration to know like this is the right way or even if there is a right way for all cases yeah”. An attendee briefly asks whether Czaplicki has reviewed another reactive framework from literature, but Czaplicki notes that he is unfamiliar with it.

- **Modularity debate**: Component encapsulation can be pursued either through higher-order/arrowized FRP constructs or through purely functional Model-Update-View architectures. - **Nested Elm Architecture**: Hierarchical reuse is achieved by nesting a child module's state, update function, and view function within a parent module. - **Ecosystem maturity**: Czaplicki highlights that UI modularity patterns in functional languages are still an active area of experimentation without a single settled standard.

---

[38:36](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2316s) Q&A: Push vs. Pull Semantics and Backpressure in UI Systems

Addressing a question about how the classic "push versus pull" distinction maps onto his FRP taxonomy, Czaplicki remarks that the terminology often creates unnecessary confusion. While the asynchronous data flow community often speaks of "hot versus cold" observables to describe behavior when observers are absent, push versus pull traditionally refers to the direction of data flow evaluation. Elm employs an event-driven push architecture: incoming user interactions immediately push updates downward through the signal graph. In contrast, pull systems initiate evaluation from the display output upward, pulling and sampling values from upstream nodes at specific timestamps.

Czaplicki argues that the theoretical divide between push and pull is largely overstated in practice: “the argument we've made in in the work we've done on FRP is that uh a push system can model a pull one in that when you get to the bottom of your program one way to say it is I pull the values through another way to say it is I trigger a push through”. Because triggering a push from the boundary produces an identical result to pulling on demand, the practical differences are minimal.

A final question asks how FRP graphs handle producers that emit data faster than the system can process—commonly known as backpressure. Czaplicki explains that Elm is intentionally tailored for user interfaces, where inputs originate from human beings clicking or typing:

> “in the domain of um UI uh you're dealing with human input so you're not going to be getting in that situation very often so a lot of these systems are prioritized towards use cases like that”

Because human interaction speeds rarely overwhelm the evaluation loop, built-in backpressure mechanisms have not been a primary architectural concern for Elm. Noting that he has run slightly over his allotted time, Czaplicki wraps up the session and invites attendees to continue the discussion outside.

- **Push vs. pull equivalence**: Push systems propagate events downstream from inputs, while pull systems sample upstream on demand; functionally, a pull request can be modeled as triggering a push update. - **Hot vs. cold distinction**: In asynchronous data flow, "hot" streams emit regardless of subscribers, whereas "cold" streams generate items only upon active subscription. - **Backpressure in Elm**: Because Elm focuses primarily on graphical user interfaces driven by human input rates, complex backpressure and rate-limiting pushback mechanisms are not prioritized in its core design.

## Notable quotes > [01:11](https://www.youtube.com/watch?v=Agu6jipKfYw&t=71s) what FRP is about is rather than using callbacks and these kind of ad hoc ways of structuring time um we actually have a system that that gives it explicit structure that you can talk about in a in a more concise way — Evan Czaplicki

## Mentioned Evan Czaplicki, Elm, Prezi, Strange Loop Conference, TodoMVC, Sublime Text, Mario, Haskell, OCaml, Agda, Idris, Bacon.js, Reactive Extensions (RxJS), ReactiveCocoa, Yampa, Netwire

## Video description Functional Reactive Programming (FRP) is becoming an increasingly common way to structure event- driven code, but the term "FRP" actually refers to a large body of increasingly diverse work. This talk will quickly cover the basics of FRP, and then go into a couple different formulations of FRP that people are beginning to use. We will explore how these formulations fit together historically and theoretically. Finally, we will see how the particular variation of FRP used in Elm makes it easy to implement hot- swapping and Elm's Time Traveling Debugger.

by Evan Czaplicki (@czaplic) - Prezi

I am a functional programmer at heart, and my broad goal is to provide the tools and learning resources to make functional languages common in industry. I develop Elm 's compiler, tools, and community to make it great for projects within Prezi and in industry in general.

[Source](https://www.youtube.com/watch?v=Agu6jipKfYw)

中文简报

Elm创造者弃用动态FRP以防内存泄漏并保障时间旅行调试 纯函数式语言若支持动态可重构信号图,就不得不承担历史事件回溯引发无界内存泄漏的风险,因此静态架构对于确定性的时间旅行调试不可或缺。 Czaplicki对比了Elm的静态一阶信号图与高阶、Arrowized以及异步数据流等变体,厘清了函数式响应式编程(FRP)这一宽泛概念。他论证了Elm的设计约束——静态拓扑、无限信号以及默认同步——如何成就了可靠的Model-View-Update架构、无缝代码热替换,以及不受内存泄漏回溯问题困扰的可回放时间旅行调试。随后,演讲剖析了高阶FRP中动态切换、Rx和Bacon.js等库中的有限事件流,以及Arrowized FRP中解耦自动机的核心权衡。最后,Czaplicki提出了务实的分类法,敦促开发者根据具体的应用需求、顺序保证和领域约束来选择响应式范式,而非盲从学术光环。 Evan Czaplicki将Elm的一阶FRP与高阶FRP区分开来,指出其静态信号图和无限信号能够保持时间顺序,并消除了对历史事件缓冲的需求。 (6:17) 高阶FRP通过嵌套信号(`Signal (Signal a)`)引入了动态图重构,但引用透明性迫使其面临“无限回溯”困境,即必须保留过去事件,从而带来严重的空间泄漏风险。 (15:30) Bacon.js和ReactiveX等异步数据流库背离了经典FRP,放弃了静态拓扑、无限生命周期和同步性,转而采用有限数据流以及冷热订阅模型。 (20:53) Arrowized FRP(AFRP)通过将自动机与外部世界解耦,解决了动态切换过程中的内存保留问题,可在节点未被观察时安全冻结状态。 (23:30)

Reading summary

Elm creator Evan Czaplicki argues that Functional Reactive Programming (FRP) is not a single unified technology, but a spectrum of distinct engineering trade-offs shaped by constraints around graph topology, stream lifespans, and synchrony. First-Order FRP and the Elm Architecture (1:11) Elm relies on what Czaplicki terms first-order FRP, where user interactions flow through an explicitly structured signal graph composed of primitive building blocks: inputs from the world (`Mouse.position`), pure transformations (`lift`), past-accumulating state (`foldp`), and signal combinations (`merge`, `lift2` through `lift8`). This foundational model operates under strict design constraints: signals are infinite and globally wired to hardware, evaluation is synchronous by default to preserve event ordering, and the signal graph topology is entirely static at compile time. Because stateful nodes only inspect their immediately preceding state rather than buffering historical events, the system maintains computational efficiency. Furthermore, Czaplicki explains that this static graph structure naturally produces the Model-View-Update pattern (the Elm Architecture), separating the application state type, a pure update stepping function, and a declarative virtual DOM view pipeline. Developer Tooling Enabled by Static Graphs (10:22) A fixed topological graph guarantees that an application's structural shape remains known indefinitely, allowing for live code hot swapping and deterministic time-travel debugging. Demonstrating with a Mario game, Czaplicki shows that modifying physics parameters like gravity in Sublime Text propagates immediately into the live runtime without losing existing program state. Because the system captures a discrete log of external input events (such as directional arrow keys), developers can pause execution, scrub backwards along the timeline, and replay past events. When code edits occur, the Elm Time Traveling Debugger re-runs updated logic retroactively across the entire recorded input history, illustrating how structural predictability simplifies runtime introspection. The Memory and Equational Dilemma of Dynamic FRP (13:00) Introducing higher-order FRP allows signal graphs to dynamically reconfigure via a `join` operation on nested signals (`Signal (Signal a)`), but introduces significant operational friction. In pure functional programming, referential transparency requires that identical expressions (such as `count Mouse.clicks`) yield identical historical values regardless of when they are instantiated. Consequently, switching dynamically into an unobserved signal requires an infinite look-back over past events, which Czaplicki notes creates a severe space leak as memory grows linearly over time. While academic solutions like linear types enforce bounded memory by preventing historical backtracking, Czaplicki argues they impose complex type machinery and forfeit simple tooling. Similarly, replacing flat data structures in applications like TodoMVC with collections of nested dynamic signals (`List (Signal Task)`) adds conceptual overhead without clear architectural benefits. Alternative Paradigms: Asynchronous Streams and Automatons (20:53) Outside classical FRP, reactive libraries adopt different trade-offs. Imperative asynchronous data flow libraries—such as Bacon.js and Reactive Extensions (RxJS)—abandon static topologies, infinite signals, and referential transparency entirely. They treat signals as finite event streams with default asynchrony, introducing distinctions between 'hot' observables (which emit unprompted) and 'cold' observables (which await active subscriptions). In contrast, Arrowized FRP (AFRP), found in libraries like Yampa, Netwire, and Elm's `Automaton`, enables dynamic switching by severing signals from the external world. State is encapsulated inside decoupled step functions that process an input and return an updated automaton alongside an output value. When an automaton is disconnected from execution, its state freezes safely without memory leaks. However, because automatons cannot directly sample ambient environmental inputs, they function primarily as internal state-structuring tools rather than complete I/O frameworks. Czaplicki concludes that reactive frameworks must be evaluated against practical engineering needs: asynchronous event streams fit concurrent multi-client servers, while synchronous first-order graphs serve client-side user interfaces that prioritize deterministic ordering, maintainability, and replayable debugging. Practical UI design questions, such as component modularity and backpressure, are often addressed adequately by nested module conventions and human input limits rather than complex reactive machinery.

精炼阅读摘要

Elm 语言创建者 Evan Czaplicki 梳理了函数式反应型编程(FRP)的多种流派,指出技术范式并非非此即彼的优劣升级,而是工程取舍。他认为 Elm 采用的静态一阶信号图虽牺牲了部分动态表达力,却换来了确定的时序保证、高效的执行性能以及时间旅行调试等关键工程优势。 Elm 一阶 FRP 的运行机制与约束 (1:11) Elm 的一阶函数式反应型编程旨在取代 JavaScript 中混乱的回调函数,为时间维度提供显式结构。该模型建立在四个基本原语之上:将外部环境输入建模为连续时间信号(如 `Mouse.position`)、通过纯函数 `lift` 转换信号、利用 `foldp`(从过去折叠)进行状态累加,以及使用 `merge` 或 `lift2` 至 `lift8` 组合多个信号。其核心在于静态约束:信号图的拓扑结构在启动时完全固定且全局已知;信号代表硬件等外部实体,不可动态增删;事件处理默认保持严格同步,确保如按键顺序不发生交叉错乱。 这种静态与同步的设计直接催生了 Elm 架构(Model-View-Update)。系统由管理完整应用状态的 Model、处理输入并步进状态的纯函数 Update、声明式渲染虚拟 DOM 的 View,以及驱动循环的输入信号组成。此外,由于信号图静态且可预测,开发者在修改代码时无需丢失当前运行状态即可热替换逻辑,甚至能记录外部输入历史,通过时间旅行调试器随意倒带、重放事件流并即时观察物理参数变化后的推演效果。 高阶 FRP 的动态图难题与内存泄漏 (13:00) 当函数式程序员尝试通过引入处理嵌套信号(`Signal (Signal a)`)的 `join` 操作符以支持动态信号图时,便进入了高阶 FRP 领域。尽管动态切换看似具备更高的表达力,但纯函数语言严格要求的等式推理和引用透明性却带来了严重的工程灾难。Czaplicki 举例说明:若程序在点击计数器与常量 0 之间切换,在切为 0 期间发生的用户点击,依照引用透明原则必须在切回计数器时被全额计入(例如产生总和 10,而非重置为 0 或从上次位置 5 恢复)。 这意味着运行时必须具备“无限向后回溯”的能力:即使某些信号从未被求值或长期处于非监听状态,系统也必须无限期保留其历史上发生的全部事件,以便未来可能进行的重建。这种需求会导致内存占用随运行时间呈线性膨胀,形成严重的内存泄漏(Space Leak)。学术界(如有关在有界空间内实现高阶 FRP 的论文)尝试引入线性类型系统来静态禁止无限回溯,但代价是极高的类型系统复杂度,且使代码热替换和时间旅行调试几乎无法实现。 命令式异步数据流与 Hot/Cold 语义 (20:53) 当反应型编程概念被移植至命令式环境(如 Bacon.js、RxJS)时,便演变为异步数据流架构。这类库彻底放弃了静态信号图和无限长信号的假设,将信号视为有限的事件流,并在生命周期结束时终止。由于不受纯函数和引用透明性的约束,重新订阅或创建流时系统不会保证结构共享,而是生成全新的运行时实例,由垃圾回收机制清理未引用的节点。 为了解决未被监听信号的生命周期与执行问题,响应式扩展类库引入了“热”(Hot)与“冷”(Cold)信号的概念:热信号无论是否有订阅者都会持续推送事件,而冷信号则在观察者显式订阅前保持休眠。Czaplicki 指出,此类异步优先的系统非常适合处理服务端并发请求,但在需要严格时序一致性和确定性调试的前端界面中,异步优先往往会增加不必要的心智负担与竞态风险。 Arrowized FRP 与自动机的状态隔离 (23:30) 箭头化函数式反应型编程(Arrowized FRP,如 Haskell 的 Yampa、Netwire 及 Elm 的 Automaton 库)采取了另一条折中路径:它打破了信号必须直接绑定外部世界的假设,转而将状态计算建模为独立的步进自动机(Automaton)。自动机类似于接收输入并输出变换结果的黑盒,通过 `pure` 包装纯函数,通过 `state` 维护类似 `foldp` 的内部累加器,并通过操作符进行管道式串联。 这种架构在动态切换时表现出显著优势。当一个处于非活跃状态的自动机被移出执行链路时,由于它本身脱离外部世界,其内部状态会自然冻结,既不会在后台空转消耗 CPU,也不会因无限回溯产生内存泄漏;调用步进操作时,调用者会同时获得当前的输出值与一个用于后续处理的全新自动机。然而,由于无法直接引用键盘或鼠标等底层事件,外部输入必须手动采样并传入,使 Arrowized FRP 更偏向于代码结构组织工具(如音频处理和游戏循环),而非完整的 I/O 运行时解决方案。 架构分类取舍与前端模块化工程实践 (27:59) Czaplicki 强调不同 FRP 流派并非非此即彼的对立关系,而是分布在由拓扑结构(静态 vs 动态)、信号生命周期(无限 vs 有限流)以及时序模型(默认同步 vs 异步)构成的设计空间中。在实践中,Elm 保持一阶核心以换取开箱即用的时间旅行调试,而在 Prezi 等实际工程中,团队可以将 Elm 编写的保证严格时序的 UI 组件作为黑盒,嵌入到外部采用 Bacon.js 或 Rx 的异步数据流架构中;同时 Arrowized FRP 亦可作为常规库在 Elm 顶层运行。 在面对应用组件模块化问题时,针对是否应引入高阶 FRP 将任务列表建模为 `List (Signal Task)`,Czaplicki 表达了审慎怀疑,倾向于使用标准的函数与嵌套 Elm 架构——即让子模块分别暴露 Model、Update 和 View 并由父模块组合调用。针对推(Push)与拉(Pull)的辨析,他认为两者在工程上等价(通过边界触发的 Push 即可模拟 Pull);至于界面开发中的背压问题,由于系统主要受限于人类有限的点击和击键速率,UI 系统无需过度设计复杂的反压流控机制。 Czaplicki 总结认为,选用何种反应型架构应完全由实际工程需求决定,而非盲从高阶理论或库的宣传话术。尽管学术界对高阶动态信号图与复杂类型系统进行了深入探索,但在图形用户界面领域,以静态信号图、同步优先和嵌套函数模块为核心的一阶模型,在维护性、性能和调试体验上依然展示出极具竞争力的工程实用价值。

Detailed notes — zh

## Background 该视频由 Strange Loop Conference 频道于 2014 年 9 月 21 日发布在 YouTube 上,演讲者为 Elm 语言设计者 Evan Czaplicki。视频简介指出,本次演讲旨在剖析函数式反应型编程(FRP)的基础与多种实现流派,梳理它们在历史与理论层面的演变脉络,并展示 Elm 所采用的特定 FRP 变体如何实现热替换以及时间旅行调试器。 ## TL;DR Evan Czaplicki 系统性梳理了函数式反应型编程(FRP)的设计空间,将其划分为一阶静态 FRP、高阶 FRP、异步数据流以及 Arrowized FRP 等核心流派。他深入解析了 Elm 所坚持的“静态信号图、无限信号与默认同步”三大约束,阐明了正是这种克制的设计才让热替换、时间旅行调试器以及经典的 Elm Architecture(Model-Update-View)成为可能。同时,演讲客观剖析了高阶 FRP 追求动态图切换所面临的无限历史回溯与内存泄漏困境,以及命令式异步数据流(如 Rx)放弃引用透明所做出的工程权衡,倡导开发者在评估响应式方案时应立足于具体的业务场景与时序保证。 ## Atmosphere & style 整场演讲呈现出一种典型的技术布道与学术思辨相融合的务实风格。Evan Czaplicki 语气轻松幽默,常以《神秘博士》里的“时间领主”自嘲,但切入类型系统与状态语义时又极其严谨透彻。现场展示马里奥重力热替换与时间旅行回放时互动生动、充满极客趣味,而在随后的问答环节中,他直面理论推导与工程实现的权衡冲突,展现出极其坦率、不盲目崇拜复杂概念的工程师底色。 ## Bulletin - [01:07](https://www.youtube.com/watch?v=Agu6jipKfYw&t=67s) Evan Czaplicki 梳理了 FRP(函数式反应型编程)的设计空间,指出 Elm 的“一阶 FRP”采用静态图、无限信号与默认同步,借此实现了开销极低的热替换与时间旅行调试器。 - [15:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=930s) 高阶 FRP 虽然引入了动态图与 join 原语,但在纯函数式语言中为了维护引用透明性,极易引发随时间线性膨胀的内存泄漏陷阱(无限回溯)。 - [20:53](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1253s) 命令式响应式库(如 Rx 和 Bacon.js)通过放弃纯函数引用透明与同步保证,选择动态图、有限事件流与异步机制,切入了截然不同的架构权衡点。 - [36:36](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2196s) 针对 UI 模块化与组件复用,Evan Czaplicki 主张返璞归真,直接使用普通的模块系统与纯函数(Model-Update-View),无需引入复杂的高阶动态图切换。 ## Key takeaways - [01:07](https://www.youtube.com/watch?v=Agu6jipKfYw&t=67s) FRP 的核心价值在于用显式的结构替代混乱的回调地狱与临时的时间处理机制,使开发者能以简洁的方式表达时间流转。 - [01:07](https://www.youtube.com/watch?v=Agu6jipKfYw&t=67s) Elm 的一阶信号图由原始输入(Inputs)、纯变换(lift)、状态折叠(foldp)以及信号合并(merge / lift2)等基础原语搭建而成。 - [06:17](https://www.youtube.com/watch?v=Agu6jipKfYw&t=377s) Elm 一阶 FRP 的四大核心约束为:信号与真实世界直接相连、信号是无限的、信号图完全静态、默认保持同步。 - [07:55](https://www.youtube.com/watch?v=Agu6jipKfYw&t=475s) 静态信号图的约束自然催生了 Elm 架构(TEA):应用由 Model、纯更新函数 Update、纯视图 View 和 Inputs 构成,数据流向清晰且无需担心内存泄漏。 - [10:22](https://www.youtube.com/watch?v=Agu6jipKfYw&t=622s) 由于程序拓扑在编译时即完全确定,Elm 能够低成本实现代码热替换以及结合历史事件重放的时间旅行调试器。 - [13:00](https://www.youtube.com/watch?v=Agu6jipKfYw&t=780s) 高阶 FRP 本质上允许在运行时动态重构信号图,通过 join 将“信号的信号”展平,但随之引出未被观察信号该如何运转等理论难题。 - [15:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=930s) 在纯函数式语义下,动态创建新信号若要满足等式推导,必须回溯历史上所有过去的事件,导致系统内存占用随运行时间呈线性无限膨胀。 - [18:01](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1081s) 学术界虽尝试利用线性类型系统(Linear Types)来限制 join 并解决空间泄漏,但这带来了极高的类型复杂度,且缺乏强力证据证明其在 UI 领域的架构优越性。 - [20:53](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1253s) 以 Bacon.js 和 Reactive Extensions(Rx)为代表的命令式异步数据流,舍弃了静态图、无限信号与默认同步,改用冷热信号机制来解决未订阅状态下的行为问题。 - [20:53](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1253s) 命令式 FRP 放弃了引用透明性保证,每次调用均创建全新实例,从而避免了纯函数式高阶 FRP 的历史回溯包袱。 - [23:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1410s) 基于箭头的 Arrowized FRP(如 Yampa、Netwire、Elm Automaton)通过切断信号图与外部世界的直连,以可串联的自动机组织代码,移出管线的自动机状态自动保持冻结。 - [23:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1410s) Arrowized FRP 无法直接表达鼠标位置等真实世界客观存在的信号,其实质是一种代码组织和计算抽象工具,而非端到端的全栈系统架构。 - [27:59](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1679s) 评估 FRP 方案时应立足于应用实际需求,重点考察系统在时间维度上的两个关键属性:是否默认保持因果时序的同步,以及如何显式控制异步 IO。 - [31:29](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1889s) 面对服务并发场景,Rx 式的默认异步方案更为契合;而面对注重时间旅行调试与状态可预测性的 UI 应用,同步静态图模型优势明显。 - [38:36](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2316s) 在 UI 场景下,事件大多由人类低频操作产生,生产者速率远低于处理能力,因此背压(Backpressure)机制并非当时 Elm 的核心关切点。 ## Detailed walkthrough ### [00:01](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1s) 演讲背景与探索 FRP 的核心目标 Evan Czaplicki 登台开场,介绍自己是 Elm 编程语言的设计者。在过去几年深入思考函数式反应型编程(Functional Reactive Programming, 简称 FRP)的过程中,他积累了大量认知与教训。本次演讲的目的,就是将这些年的摸索精炼成 40 分钟的内容,免得大家再去踩他经历过的那些坑。谈到演讲标题《掌控时间与空间》(Controlling Time and Space),Evan Czaplicki 笑着解释说,起这个名字是因为他在实现 Elm 的时间旅行调试器(Time Traveling Debugger)时,感觉自己就像《神秘博士》里的“时间领主”(Time Lord)一样掌控了时间与空间。不过,演讲的核心重点其实非常务实:搞清楚 FRP 究竟是什么,因为业界目前给这个词赋予了太多不同甚至互相冲突的含义。 整场演讲设定了三个明确目标:首先是弄清楚 FRP 的本质;其次是如何在这一庞大的概念保护伞下对各种分支与流派进行归类;最后是如何以合理的方法对这些不同的实现进行评估。Evan Czaplicki 计划先从他所谓的“一阶 FRP”(first-order FRP)切入——这基本上就是 Elm 当时的运作方式——随后再逐步延伸到在此核心之上演化出的各种变体。 --- ### [01:07](https://www.youtube.com/watch?v=Agu6jipKfYw&t=67s) 一阶 FRP 与信号图(Signal Graph)的原语 在探讨更复杂的理论之前,Evan Czaplicki 首先拆解了 Elm 所代表的一阶 FRP 究竟如何运作。构建交互式应用的核心需求,是处理来自外部世界的互动:用户在屏幕前敲击键盘、移动鼠标,系统接收到按键与鼠标事件后送入处理管道。在普通的 JavaScript 中,这通常会退化成一团由回调函数交织而成的“回调地狱”(tangle of callbacks);而 FRP 的核心初衷,就是用具有明确结构的方式来替代这些临时的事件处理机制。Evan Czaplicki 对此强调说:“FRP 的核心在于,我们不再使用回调函数和那些临时拼凑的时间组织方式,而是拥有一个能够赋予时间显式结构、让你能以更简洁方式讨论时间的系统。”(“Sort of what FRP is about is rather than using callbacks and these kind of ad hoc ways of structuring time, we actually have a system that gives it explicit structure that you can talk about in a more concise way.”) ```mermaid flowchart LR accTitle: 一阶 FRP 信号图基本数据流 accDescr: 外部输入流向信号处理原语,再输出为屏幕渲染或网络请求 subgraph Inputs ["外部世界输入 (Inputs)"] direction TB M["Mouse.position"] K["Keyboard.lastPress"] end subgraph Processing ["信号图处理 (Signal Graph)"] direction TB T["lift (纯变换)"] S["foldp (状态折叠)"] C["merge / lift2 (信号合并)"] end subgraph Outputs ["外部反馈 (Outputs)"] direction TB V["HTML / 视图渲染"] H["HTTP 请求 / LocalStorage"] end Inputs --> Processing Processing --> Outputs ``` 在 Elm 中,这个处理系统被称为“信号图”(Signal Graph)。信号图由以下几类基础构件搭建而成: * **世界输入(Inputs)**:最底层的原始输入,例如 `Mouse.position`。它的类型不是普通的一对整数,而是 `Signal (Int, Int)`,代表着一个随着时间不断更新的坐标对;再如 `Keyboard.lastPress`,代表按下的最后一个按键。Evan Czaplicki 举例说,当他在键盘上敲下 "hello strange loop" 时,信号值按序改变;中途如果分心去上网浏览了一会儿,值就会停滞,直到他回来继续输入才继续推进,这让时间维度的变化有了显式的表达。 * **信号变换(`lift`)**:如果我们想对原始输入进行转换,可以使用 `lift` 函数。它接收一个从类型 `a` 到 `b` 的普通函数,将 `Signal a` 转换为 `Signal b`。例如传入一个判断是否为辅音的函数 `isConsonant`,就能将按键字符信号实时转换为布尔值信号流。 * **状态处理(`foldp`)**:在 Elm 中管理状态依赖名为 `foldp` 的原语,全称为“fold from the past”(从过去折叠),命名故意仿照函数式编程里的 `foldl` 与 `foldr`。它接收一个累加函数 `a -> s -> s` 和一个初始状态,每当输入信号产生新值,就结合上一次的状态计算出新状态。最基础的例子就是每次按键将计数器递增 1,而复杂的场景则可以用它来驱动整个待办事项(TodoMVC)应用的状态流转。 * **信号合并(`merge` 与 `liftN`)**:将多个信号汇聚在一起。最基础的是 `merge`,将两个同类型信号直接合并;另一个是 `lift2`(Evan Czaplicki 调侃说这是一个命名值得商榷的原语),它在任意时间点将一个二元函数同时作用在两个信号的新值上。比如将 X 坐标信号与 Y 坐标信号组合,或者将窗口尺寸与鼠标坐标组合。Elm 还提供了 `lift3` 一路到 `lift8`,并在需要更多参数时可以通过函数柯里化技巧进一步扩展。 --- ### [06:17](https://www.youtube.com/watch?v=Agu6jipKfYw&t=377s) Elm 一阶 FRP 的核心设计原则与约束 梳理完基本 API 后,Evan Czaplicki 指出,光看这些操作细节还无法理解其背后的设计哲学。深入其核心,会发现 Elm 的一阶 FRP 具有四项非常关键、却并非在所有 FRP 实现中都通用的特质: 第一,**信号与世界直连(Signals are connected to the world)**。程序中的信号有底座支撑,直接映射到物理硬件或浏览器环境中的事件流,如鼠标坐标或键盘按键。 第二,**信号是无限的(Signals are infinite)**。这是一个微妙却极其关键的属性:在 Elm 中没有“销毁一个信号”或者“创建之后再丢弃一个输入”的操作。Evan Czaplicki 形象地比喻道,这就好比你不能说“我刚才用着鼠标,突然就把鼠标物理上从笔记本里彻底抠掉了”。Evan Czaplicki 直截了当地指出这一限制的本质:“本质上,这意味着你程序的输入集合是固定不变的。”(“Essentially it's saying the inputs to your program are fixed.”) 第三,**信号图是完全静态的(Signal graphs are static)**。从程序启动的那一瞬间开始,直到遥远的未来,应用的信号拓扑图结构是完全确定且不可变的。虽然听起来“动态信号图”似乎更加灵活,但 Evan Czaplicki 强调,动态性会带来巨大的权衡与代价,而静态结构反而赋予了 Elm 极强的底层掌控能力。 第四,**默认同步(Synchronous by default)**。当事件在分支网络中传递时,次序至关重要。如果用户敲下了 "hello",观察者必须按严格的发生顺序看到字符流转。Elm 默认维持这种同步与确定性的因果顺序,而在某些反应型变体中这一性质是缺失的。 --- ### [07:55](https://www.youtube.com/watch?v=Agu6jipKfYw&t=475s) 架构与性能的收敛:Elm 架构(TEA)的诞生 上述几项严格的约束条件,直接为系统带来了两个显著的正面收益:运行效率与清晰的应用架构。 在效率方面,由于信号图是完全静态且事件驱动的,当外界事件进入时,它只会沿着既定拓扑单向流动;有状态节点(`foldp`)在运算时仅需查看前一个状态即可生成新状态,完全无需回溯整个历史链条或维护庞大的垃圾回收开销。在静态图的约束下,系统既不会泄漏过去的事件,也不需要复杂的调度机制。 在架构层面,静态信号图自然而然地塑造了一种纯粹的应用组织模式。Evan Czaplicki 现场展示了一个使用 LocalStorage 持久化存储的 TodoMVC 应用,以此证明在这种模型下与外部真实世界交互其实非常简洁。他放大代码视图,向大家展示任何 Elm 程序都会自然拆分出的四个核心部分: * **Model(数据模型)**:显式且精准地定义应用程序的内部状态。例如任务列表是一个包含若干 Task 的 List,而每个 Task 明确包含描述文本、完成状态、是否处于编辑模式等字段。 * **Update(状态更新)**:一个纯函数,定义了应用状态如何随着用户的操作意图向前步进,例如处理“添加任务”或“删除任务”等动作。 * **View(纯函数视图)**:负责将当前的 Model 纯粹地投射为屏幕上呈现的 HTML。开发者可以定义极具复用性的组件函数(例如渲染单个任务项),然后通过 `map` 直接将它应用在整个可见任务列表上,以完全声明式的方式构建 UI。 * **Inputs / Signals(信号输入)**:把视图中的交互或外部事件重新接驳回更新循环。 Evan Czaplicki 总结道,这种被广大 Elm 开发者熟知的架构规范并非凭空捏造,它的根源正是静态信号图对开发者的引导:“那种架构、那种结构来源于静态信号图——它们引导着你以这种方式去编写应用程序。”(“That architecture, that structure comes from the static signal graphs—they guide you towards writing your applications that way.”)这为接下来探讨为什么其他 FRP 变体会产生截然不同的架构风格打下了关键的基桩。 ### [10:22](https://www.youtube.com/watch?v=Agu6jipKfYw&t=622s) 热替换与时间旅行调试器实机演示 静态信号图带来的巨大优势之一是“热替换”(hot swapping)。Evan Czaplicki 演示了前一年的经典示例——Mario(马里奥)程序,指出这是在十分钟内快速学习 Elm 的好方法。在演示中,他先让马里奥走到屏幕边缘,随后直接在代码中修改重力参数并保存;随着程序持续运行,玩家再次让马里奥起跳,可以立即感受到重力变得更强。修改后的代码逻辑在无需重启应用的情况下实时生效,变更自动向下游传播。 随后 Evan 展示了 Elm 的时间旅行调试器(Time Traveling Debugger)。虽然社区此前已见过原型,但当时即将发布一个易于安装的新版本。调试器不仅允许开发者随时暂停程序,还能拖动时间轴自由回退到任意历史时间点,从过去的某一刻重新开始操作(例如原本向左走,回滚后改成向右走)。 ```mermaid flowchart LR accTitle: "Elm 时间旅行调试与热替换流程图" accDescr: "展示静态信号图下输入事件驱动状态更新,并结合时间旅行回滚与热替换的循环流程" subgraph Runtime["运行时状态"] History["事件历史队列"] State["当前程序状态"] end subgraph Debugger["调试与重放"] TimeTravel["时间轴滑块<br/>(回滚 / 前进)"] CodeEdit["编辑器修改代码<br/>(如重力参数)"] end TimeTravel -->|"检视历史并重置"| History CodeEdit -->|"热替换重算整条历史"| State History -->|"根据新代码顺序重放"| State ``` 该调试器还提供了实时的数据检查能力。通过在时间轴上逐步查看,可以清晰捕捉到特定帧的输入状态,例如何时按下了右方向键、何时同时按下了上和右、何时松开了按键。更强大的是热替换与时间旅行的结合: - Evan 在 Sublime Text 中调整重力参数后按下保存; - 该变更不仅作用于当前帧,而是顺着整条操作历史完整重算; - 之前跳跃的轨迹瞬间更新,马里奥在重算后的历史中跳得异常夸张,开发者随后能将其调回合理的数值。 关于静态图为何能支撑这些功能,Evan Czaplicki 强调: > “A lot of these abilities to swap in code and replay code comes from the fact that we know what if we know what the program looks like at the start, we know what it's going to look like in an hour, in a day, and we don't have to do some kind of crazy complex analysis to figure that out or to match things up.” --- ### [13:00](https://www.youtube.com/watch?v=Agu6jipKfYw&t=780s) 高阶 FRP 的核心问题与 join 函数 介绍完一阶 FRP(First-Order FRP)的静态信号图特性后,Evan 提出了社区中持续探讨的核心问题:如果信号图可以在运行时动态重构会怎样?什么是高阶 FRP(Higher-Order FRP)?人们天然会认为“高阶肯定比一阶更好”,但实际上这引发了一系列极其困难的研究挑战。不同分支的探索并非单纯的孰优孰劣,而是特性的权衡取舍。 高阶 FRP 本质上是放弃了“信号图必须保持静态”的约束。在 API 层面,这种动态能力通常由一个名为 `join` 的函数来表达,其类型签名接收一个“信号的信号”(`Signal (Signal a)`),并将其展平为单个普通信号(`Signal a`): ```mermaid flowchart TD accTitle: "Join 函数的工作机制" accDescr: "外层信号负责选择当前监听的内层输入信号,并将其展平输出" Outer["外层选择信号<br/>Signal (Signal a)"] SigA["内层信号 A"] SigB["内层信号 B"] Out["输出信号<br/>Signal a"] Outer -->|"动态切换当前监听目标"| Out SigA -.->|"被选中时流向输出"| Out SigB -.->|"被选中时流向输出"| Out ``` 这直观上可以理解为:程序面对一组输入信号,并根据外层信号的更新,动态决定当前去监听哪一个信号。但这立即引出了一连串棘手的问题: - 当一个信号当前没有被任何下游监听时,它应该继续在后台保持运行,还是应该完全暂停? - 如果程序在运行途中动态创建并接入了一个此前从未被观察过的新信号,系统的状态该如何计算? --- ### [15:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=930s) 等式推导、无限回溯与内存泄漏陷阱 为了具象化高阶 FRP 带来的隐患,Evan 举了一个计数器的例子: 1. 存在一个简单的鼠标点击计数信号,每次点击数值递增 1; 2. 构造一个切换逻辑:当控制布尔值为 `True` 时监听点击计数信号,为 `False` 时输出常量信号 `0`; 3. 假设初始状态为 `True`,用户点击了 5 次,计数显示 5;随后切换到 `False`,输出变为 0;在 `False` 状态下用户又狂点了 5 次;最后再次切回 `True`。 此时,信号的值应该是什么?是重新归零(0)、从断开处恢复(5),还是应用运行以来的总点击数(10)? 在纯函数式语言中,由于追求“等式推导”(Equational Reasoning)与指称语义的一致性,相同的表达式应当产生相同的行为。因此,语言语义往往强制要求其输出整个生命周期内的完整点击总数(即 10 次)。 这正是灾难的根源所在。设想用户启动应用时处于 `False`,界面显示 0;过了 10 分钟或 20 分钟,用户疯狂点击了成千上万次,随后程序首次动态实例化并切换到了该点击计数信号。由于该信号需要保持纯函数式语义,它必须感知到之前发生过的所有点击。Evan 总结道: > “Essentially the problem is creating a new signal may need infinite look back. I may need to look back at all the previous events to be able to reconstruct the signal so that I have these properties that I want. Another way to say this is memory growth is linear with time.” 为了防备未来某个从未存在的信号可能需要“回溯历史”,系统必须持续记录过去发生的每一个事件与时间步,导致内存消耗随时间线性无限膨胀。 --- ### [18:01](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1081s) 约束切换能力的研究路径与架构取舍 面对无限回溯与空间泄漏问题,研究界的结论并非彻底否定动态切换,而是去寻找如何利用类型系统限制 `join` 的定义,使其仅允许切换到具有“安全历史深度”的信号上。 在相关学术成果中,最典型的代表是题为《Bounded Space 中的高阶函数式反应型编程》(*Higher-Order Functional Reactive Programming in Bounded Space*)的论文。该方案引入了线性类型(Linear Types)系统。虽然线性类型在 Haskell、OCaml 或 Agda 中并不常见,但作者借助它成功从类型层面证明了:除非开发者显式声明保存历史,否则无法写出需要无限回溯的程序。 然而,这些理论突破伴随着沉重的工程代价: - **丧失简易性**:放弃静态信号图后,热替换、时间旅行调试器以及清晰直观的应用架构变得极难实现; - **类型与 API 复杂度陡增**:开发者必须应对复杂得多的类型标注与认知负担。 Evan 回顾了此前展示的 TodoMVC 架构。即使高阶 FRP 的所有理论缺陷都被完美解决,在实际构建 UI 应用时,开发者是否真的需要把任务列表建模成“信号的列表”(`List (Signal Task)`)?这种动态信号网络可能会让代码结构急剧复杂化,而目前并没有强有力的证据表明这种架构风格优于简单清晰的一阶状态更新模式。随后,Evan 将话题转向业界另一种常见的响应式方案——异步数据流(Asynchronous Data Flow,如 ReactiveX / ReactiveCocoa)。 ### [20:53](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1253s) 命令式语言中的异步数据流:Bacon.js 与 Reactive Extensions 在命令式语言中,任何看起来像 FRP 的库(例如 Bacon.js 或 Rx/Reactive Extensions)通常都归属于异步数据流这一分类。如果对照最初设定的核心设计维度,这类方案做出了明显的取舍:它们首先舍弃了“信号图是静态的”这一限制,因为动态图是许多实际场景的刚需;其次放弃了“信号是无限的”假设,这意味着事件流可以主动终止——即一个信号启动后触发 4 次事件便可自行消亡;最后,它们放弃了“默认同步”的原则,转而采用异步机制(但这并不意味着完全无法实现同步,只是必须由开发者自行处理)。这类库通常提供类似 `flatten` 的函数,将“信号的信号”(signals of signals)拍平。Evan Czaplicki 指出,在命令式语言中,开发者拥有为概念起更直观名称的自由,例如把拍平高阶信号直接称为 `flatten` 就非常形象。 ```mermaid flowchart TD accTitle: 核心设计维度的取舍对比 accDescr: 对比 Elm 的纯静态同步设计与命令式 FRP 的动态异步设计差异。 subgraph ElmDesign["Elm 核心设计约束"] E1["静态信号图 (Static)"] E2["无限信号 (Infinite)"] E3["默认同步 (Synchronous)"] end subgraph ImperativeDesign["命令式 FRP (Rx / Bacon.js) 取舍"] I1["动态信号图 (Dynamic)"] I2["有限事件流 (Can End)"] I3["默认异步 (Asynchronous)"] end ElmDesign -.->|"放弃限制 / 重新权衡"| ImperativeDesign ``` 这种设计取舍改变了语义保证。Evan Czaplicki 强调这并非简单的放弃,而是“决定瞄准设计空间中的另一个不同点”(“we've decided to shoot for a different point in the design space”)。在此前探讨的纯函数式代码中,如果反复求值类似 `count mouseClicks` 的表达式,会因为共享状态与引用透明引发困扰;而在命令式语言中,系统压根没有提供引用透明的保证。“在命令式语言里,从来没人说过你会拥有那种保证,因此每次创建信号时,你都在创建一个全新的信号”(Evan Czaplicki:“now we're in an imperative language and no one ever said you were going to have that guarantee so whenever you create one of these signals you're creating a totally new one”)。举例来说,当某个条件切换至 `true` 时,计数器从 0 累加到 5;切换到 `false` 显示 0;再度切回 `true` 时,它会重新建立一个全新的信号从 0 开始计数,旧信号则依靠底层垃圾回收机制进行清理。与此同时,“没有订阅者时信号该如何表现”的问题依然存在,Reactive Extensions 采用“冷热信号”(Hot and Cold Observables/Signals)来解决这一问题:热信号无论是否有下游监听都会持续产生数据,而冷信号在没有监听者时则会停止产生数据。 - 命令式 FRP 库(如 Bacon.js、Reactive Extensions)的核心特征是:动态图、事件流有限、默认异步。 - 每次在命令式上下文中调用生成信号的操作,都会分配全新的独立信号实例,而非复用已有全局节点。 - 热信号(Hot Signal):即使没有任何下游监听者,也会持续在后台生成并广播数据。 - 冷信号(Cold Signal):只有当下游出现观察者/监听者时,才会激活并开始输出数据。 --- ### [23:30](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1410s) Arrowized FRP 与自动机:与真实世界解耦的纯粹代码结构 除了高阶 FRP 与异步数据流,设计空间中还有另一条著名分支——Arrowized FRP(基于箭头的 FRP)。这一流派在 Haskell 生态中历史悠久,早期的代表作是 Yampa,较为现代的发展则是 Netwire,而 Elm 也通过内置的 `Automaton`(自动机)库实现了相同的理念。Arrowized FRP 在核心设计中同样划掉了“信号图必须是静态的”,但它最具创新性的选择是划掉了“信号图与真实世界直接相连”。这一巧妙的解耦带来了一系列非常独特的性质。 为了解释这一概念,Evan Czaplicki 以 Elm 的 `Automaton` API 为例进行了拆解。可以将自动机想象成一个个处理数据的小机器人(Little Robot): 1. **纯变换(`pure`)**:接受一个纯函数并封装为自动机。例如一个将输入值加 1 的自动机,传入 1 输出 2,传入 42 输出 43,传入 100 输出 101。其代码形式就是一个接收输入并递增的普通纯函数。 2. **状态维护(`state`)**:类似于信号世界的 `foldp`。自动机在内部持有基础状态(如初始值 0),并接收步进函数。每当外部送入一个值(例如字符串 `"hello"`),自动机就将内部状态加 1 并输出当前总数 1;若再收到 `"how are you"`,内部计数递增为 2 并输出。 3. **管道串联(Chaining)**:两个处理信息的自动机可以像管道一样头尾相连。一个负责将类型 `A` 转换为 `B` 的自动机,与一个将 `B` 转换为 `C` 的自动机串联后,即可完成从 `A` 到 `C` 的处理。两个 `+1` 自动机串联,便构成了一个 `+2` 的自动机。 ```mermaid flowchart LR accTitle: Arrowized FRP 自动机切换与状态暂停机制 accDescr: 展示自动机接入活跃处理管线时状态累加,移出管线后内部状态保持冻结的机制。 subgraph ActivePipeline["当前激活管线"] Input["事件输入流"] --> NodeA["自动机 A (内部计数: 5)"] NodeA --> Output["结果输出"] end subgraph InactiveNodes["未接入世界的离线自动机"] NodeB["自动机 B (状态保持: 3)"] NodeC["自动机 C (状态保持: 0)"] end NodeA -.->|"被动态切出管线 (状态冻结在 5)"| InactiveNodes NodeB -.->|"被动态接入管线 (从 3 继续计数)"| ActivePipeline ``` 当引入动态切换时,Arrowized FRP 的真正优势显现出来。假设有数个内部初始计数均为 0 的计数自动机,程序将其中一个放入活跃管线中处理事件。当 5 个事件流过时,该自动机的状态递增至 5。此时如果通过切换机制将它从管线中拔下,换上另一个初始状态为 0 的绿色自动机,原本持有 5 的节点由于“根本不与外部世界直接相连”,因此当它被移出图之后,关于它是否继续运行的困惑自然化解。Evan Czaplicki 解释道:“当我们将它移出图时,‘它是否会继续运行’这个问题迎刃而解——它不会继续运行,因为它没有连接到任何东西”(Evan Czaplicki:“so when we take it out of the graph this question of what happens does it keep running it just answered uh it doesn't keep running it's not connected to anything”)。若给绿色自动机输入 3 个事件后再次换出,各个节点的状态分别冻结在 5、3 和 0。开发者可以非常自由灵活地切换状态机。 然而,Arrowized FRP 的致命代价在于它彻底放弃了直接描述外部世界信号的能力。系统中无法直接表达“当前鼠标位置”这种由外部客观存在的信号,因为一切外部输入都必须先由外部推入自动机。这意味着 Arrowized FRP 的本质并不是一套完整的端到端系统架构,而是一种用于组织代码的结构化工具,在定位上其实与纯函数、模块系统属于同层竞争关系。Evan Czaplicki 承认这种模式在合成音乐等特定领域以及 Netwire 库中取得了一定成功,但其核心价值取决于应用场景是否需要这种抽象。 --- ### [27:59](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1679s) FRP 全景分类体系与实践选型策略 综合所有讨论,FRP 的多元流派并非零和博弈,而是同一设计空间中相互补充的取舍点: | FRP 范式 | 代表语言 / 库 | 图结构特性 | 信号生命周期 | 时间语义 | 与 Elm 的集成关系 | |:--- |:--- |:--- |:--- |:--- |:--- | | **一阶静态 FRP** | Elm 核心核心实现 | 静态图 (Static) | 无限 (Infinite) | 默认同步保证 | 核心基石 | | **高阶 FRP** | Haskell 部分学术库 | 动态图 (Dynamic) | 无限 (Infinite) | 同步 | 可作为特性引入(需权衡复杂度) | | **异步数据流** | Bacon.js、Rx 系列 | 动态图 (Dynamic) | 有限可终止 (Finite) | 默认异步 | 宿主系统(可在外层包裹 Elm 组件) | | **Arrowized FRP** | Yampa、Netwire、Elm Automaton | 动态切换内部结构 | 不直接暴露信号 | 纯计算管道 | 纯库形式(可作为内部抽象工具嵌入) | 高阶 FRP 本质上是在一阶模型上增加动态创建图节点的能力,但会牺牲静态图带来的可预测性与优化空间;异步数据流放弃了同步顺序保证,更适合作为外部宿主来集成 Elm。Evan Czaplicki 以自身雇主 Prezi 为例说明:团队若想在庞大系统中使用 Elm,完全可以仅用 Elm 开发某个复杂的交互模块,并在外部使用 Bacon.js 或 Rx 进行拼装连接,在局部享受纯函数式同步保证的同时,依然能对接复杂的外部异步系统;至于 Arrowized FRP,它本身就是并且可以仅仅是一个普通的类库。 从顶层分类来看,广义 FRP 可以分为“静态图”与“动态图”两大阵营;而对于动态图流派,判断其细分属性的最快经验法则如下: - **无限信号**:通常出现在以 Haskell 为代表的纯函数式语言中; - **有限事件流**:通常出现在命令式语言中(具有明显的事件结束、取消订阅等机制); - **非信号模型**:不直接讨论信号本身的无限或有限,而是封装为计算单元(如 Arrowized 自动机)。 在实际选型与评估不同 FRP 方案时,Evan Czaplicki 提醒开发者必须重点拷问系统在时间维度上的两个关键保证:第一,“它默认是同步的吗?”(“is it synchronous by default?”),默认同步能提供严格的因果时序保障,确保用户在键盘输入 `"hello"` 后点击按钮时,系统绝不会因为某段后台处理耗时过长而导致按钮点击事件插队到字符中间响应;第二,“它是否允许异步操作?”,因为任何实际软件都无法避开 HTTP 网络请求或磁盘写入等阻塞性 IO。理想的框架(正如 Elm 所追求的)应当在保留默认同步以确保确定性的同时,以一种显式且受控的方式接纳异步 IO。 ### [31:29](https://www.youtube.com/watch?v=Agu6jipKfYw&t=1889s) 评估 FRP 方案的权衡:异步、图重构与调试体验 在评估各种函数式响应式编程(FRP)方案时,开发者不应该只追求类库上那些听起来高深炫酷的术语,而必须立足于实际工程:“and when you want to evaluate this the question isn't how can I get the fanciest like words on my on my library it's what properties do I need for my application right”。Evan Czaplicki 指出,不是所有现存的 FRP 库都明确规划了异步机制(asynchrony),尤其是在高阶 FRP(Higher-Order FRP)中,异步的处理方式格外需要留心。此外,开发者还要明确:自己能否直观地讨论输入源?系统究竟是基于箭线(arrowized)的代码组织形式,还是纯粹处理离散事件的方式?图结构是否支持动态重构(reconfigure)? 不同的应用场景对这些特性的诉求截然不同。如果你要开发服务端程序,例如面对 10 个同时连入服务器获取信息的客户端,采用类似 Reactive Extensions(Rx)那种默认异步的模式就非常合适,因为根本没理由让任何一个客户端由于其他事件的到达顺序而被阻塞;在不需要强同步保证的系统中,异步数据流的表现极为契合。但如果你看重的是 Elm 那样强大的时间旅行调试工具,你就必须做出不同的权衡。Evan 坦言自己无法断言高阶或动态图方案的调试体验究竟如何,但他提醒大家,选择技术方案不能只看起步时有多酷,而要考虑它将贯穿项目的整个生命周期;引入更多高级特性往往伴随着复杂性代价,一旦业务代码量膨胀,系统可能迅速变得难以维护,因此必须反复审视自己的应用到底能否为了更纯粹的简单性而舍弃某些特性。 ```mermaid flowchart TD accTitle: FRP 架构选型权衡 accDescr: 对比异步数据流与同步静态图在不同场景下的特性权衡 subgraph Async["异步数据流方案 (如 Rx / Bacon.js)"] A1["默认异步 / 动态图"] --> A2["适合服务端并发 / 多客户端独立拉取"] A2 --> A3["代价: 难以做时间旅行调试, 长期代码复杂度较高"] end subgraph Sync["同步静态图方案 (如 Elm)"] B1["事件驱动 / 静态图"] --> B2["适合 UI 交互 / 保持引用透明"] B2 --> B3["优势: 原生支持时间旅行调试器, 代码模型极简"] end ``` - 如果先学懂了 Elm 的核心原则,再去探索其他类型的 FRP 方案就会变得非常容易,这些原则在整个响应式领域都是通用的。 - 异步数据流虽然功能更丰富,但开发者必须权衡它带来的复杂度成本,思考项目中是否真的需要这些特性。 ### [34:01](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2041s) 问答环节:高阶 FRP 的 Join 操作与副作用建模 在进入问答环节后,现场听众提出了一个关于高阶 FRP 中 `join` 操作的核心理论问题:既然高阶 FRP 为了保持引用透明性,在展开嵌套信号时不得不追溯过去、保留历史上流经信号的所有可能值,那为什么不干脆把信号直接建模为带有副作用的(effectful),从而一了百了? Evan 回应称,如果选择将信号视为带有副作用的实体,本质上就是转向了类似 Reactive Extensions 或 Bacon.js 这种异步数据流风格。听众追问为何在高阶 FRP 中必须死守“两次调用 `count Mouse.clicks` 必须完全等价”这个等式约束,而不直接承认“信号的运行机制本就不是纯引用的”。Evan 解释道,虽然他不确定具体有哪些类库明确做出了这种妥协,但这确实是一个根本性的设计抉择(design choice)。一旦放弃纯函数的引用透明约束,你就会进入 Rx 的技术流派,面临不同的设计权衡,这与学术界坚持保持代数性质的高阶 FRP 是两种完全不同的路线。 ### [35:14](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2114s) 问答环节:箭线化 FRP(Arrowized FRP)的接入与步进机制 另一位听众提问:如果在 Elm 中使用箭线化 FRP(Arrowized FRP)接入真实信号,切换(switching)是发生在系统已经运行起来之后吗?如果是,这种运行时切换是如何权衡数据到达时机的? Evan 解释了自动机(Automaton)式 FRP 的工作方式:通常开发者会先定义好箭线化 FRP 系统的内部流向,把动态逻辑设计在系统内部,然后再将其与外部信号图连接。数据灌入后,内部状态会顺畅流动,组件也由此被切入或切出。何时接入并不是核心难题,因为无论何时向它传入数值,它都可以向前步进(step forward)。在箭线模型中,“so when you step an automaton forward what you're really getting is the value it produces and the new automaton that you should run”。每次步进都会产出一个当前结果以及一个负责处理后续输入的全新自动机,这就好比在对现实进行派生(forking reality),既推进了系统的世界状态,又保留了步进起点时的确定值。 ### [36:36](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2196s) 问答环节:Elm 的组件模块化实践与嵌套架构 有听众问及应用架构中的模块化难题:在像 Todo 待办清单这样的应用中,如果想要把其中单行任务的编辑器独立拆解出来,复用到另一个完全不同的程序中,应该如何组织架构? 针对组件封装与复用,Evan 分析了两种途径:一种是借助高阶风格或箭线化 FRP(Arrowized FRP)等可重构图技术来提供抽象并完成包裹包装;而他目前在 Elm 中力推的做法则是返璞归真,直接依靠最普通的模块系统和纯函数(module system and functions)。Evan 直言:“what I'm advocating right now is sticking with module system and functions”。具体而言,如果想将任务单项模块化,只需编写一个专注处理任务的模块,在其中显式定义好它的 Model(模型)、Update(状态更新函数)以及 View(视图渲染函数);接着,在更高层级的 Todo 列表模块中同样定义其专有的 Model、Update 和 View,以此类推。通过这种层层嵌套的架构(即日后成熟的 Elm Architecture)来获得清晰的模块化。Evan 客观地指出,目前尚不能断言哪种抽象手段绝对占优,整个领域对架构模式的探索还远未充分,或许在不同场景下根本就不存在唯一的最优解。 ```mermaid flowchart LR accTitle: Elm 嵌套模块架构 accDescr: 展示 Task 模块与 TodoList 模块如何通过 Model-Update-View 组合嵌套 subgraph TaskModule["Task 模块"] TM["Task Model"] TU["Task Update"] TV["Task View"] end subgraph ListModule["TodoList 模块"] LM["List Model (包含多个 Task Model)"] LU["List Update (调度 Task Update)"] LV["List View (组合调用 Task View)"] end TaskModule -->|"函数嵌套调用与状态聚合"| ListModule ``` - 拆分独立 UI 单元时,无需引入高阶 FRP 的复杂动态图切换,直接通过 Model-Update-View 纯函数契约进行自底向上的组合。 - 业界关于 FRP 的模块化形态仍在探索期,纯函数加模块的模式具有极高的直观性与可预测性。 ### [38:36](https://www.youtube.com/watch?v=Agu6jipKfYw&t=2316s) 问答环节:推与拉(Push vs Pull)以及背压问题的本质 在回答有关“Push(推)与 Pull(拉)在 FRP 分类体系中处于什么位置”的问题时,Evan 澄清了这些容易让人混淆的术语概念。在异步数据流讨论中,大家常谈论 Hot(热)和 Cold(冷),即当没有监听者时流是否继续发射数据;而 Push 和 Pull 描述的则是更新传播的方向:Elm 是纯事件驱动的,因此属于 Push 模型——一旦有输入产生,事件就会顺着图被向后推送并层层传播;而 Pull 模型则是由最底层的消费节点发起询问“我想看新数据”,随后逆向回溯整张图去拉取并采样该时刻的值。但 Evan 强调,Push 系统完全能够模拟 Pull 系统的行为:你可以把底层的刷新请求看作是拉取,也可以看作是触发了一次向后推送,两者在本质上并没有听上去那么巨大的鸿沟:“and so I don't think there's as huge a difference between this as the distinction isn't as important as it sounds”。 随后有观众追问系统该如何应对“生产者速度过快、下游处理不过来”的吞吐失衡问题(即背压与丢数据机制)。Evan 回答道,Elm 及其很多同类响应式系统首要解决的是前端 UI 交互领域的问题,在 UI 场景下,事件源头绝大多数是人类的操作输入(鼠标移动、点击、键盘按键),人类的操作频率极低,基本上不会陷入生产速率远超消费能力的极端情况。虽然涉及数据流回退压力的机制(push back / backpressure)在更广泛的响应式系统中有其专门的研究,但鉴于 Elm 始终聚焦于 UI 领域,背压控制并不是该语言当时的主要焦点。演讲最后,Evan 提示时间已经超出,邀请还有疑问的与会者会后到场外继续交流,并在全场掌声中结束了演讲。 ## Notable quotes > [01:07](https://www.youtube.com/watch?v=Agu6jipKfYw&t=67s) Sort of what FRP is about is rather than using callbacks and these kind of ad hoc ways of structuring time, we actually have a system that gives it explicit structure that you can talk about in a more concise way. — Evan Czaplicki ## Mentioned Evan Czaplicki, Elm, Prezi, Strange Loop Conference, Doctor Who, JavaScript, Mario, Sublime Text, TodoMVC, Bacon.js, Reactive Extensions, ReactiveCocoa, Yampa, Netwire, Haskell, OCaml, Agda ## Video description Functional Reactive Programming (FRP) is becoming an increasingly common way to structure event- driven code, but the term "FRP" actually refers to a large body of increasingly diverse work. This talk will quickly cover the basics of FRP, and then go into a couple different formulations of FRP that people are beginning to use. We will explore how these formulations fit together historically and theoretically. Finally, we will see how the particular variation of FRP used in Elm makes it easy to implement hot- swapping and Elm's Time Traveling Debugger. by Evan Czaplicki (@czaplic) - Prezi I am a functional programmer at heart, and my broad goal is to provide the tools and learning resources to make functional languages common in industry. I develop Elm 's compiler, tools, and community to make it great for projects within Prezi and in industry in general. [Source](https://www.youtube.com/watch?v=Agu6jipKfYw)

Source quotes

[1:07] Evan Czaplicki: Sort of what FRP is about is rather than using callbacks and these kind of ad hoc ways of structuring time, we actually have a system that gives it explicit structure that you can talk about in a more concise way.

Source quotes

[1:11] Evan Czaplicki: what FRP is about is rather than using callbacks and these kind of ad hoc ways of structuring time um we actually have a system that that gives it explicit structure that you can talk about in a in a more concise way

Original transcript

[0:01] I'm Evans wtky uh I designed the elm [0:04] programming language and sort of over [0:06] the course of thinking about functional [0:08] reactive programming over the past [0:10] couple years I've learned some I've [0:12] learned some things so this talk is [0:13] really distilling those years into like [0:16] a 40-minute thing so you don't have to [0:18] you don't have to go through the same [0:19] thing that I did um and I went with [0:22] controlling time and space because at [0:24] the time I was feeling like like some [0:26] kind of Time Lord with like time travel [0:28] and but the real focus is on [0:30] understanding uh what FRP is and there [0:33] actually quite there's quite a lot of [0:34] diversity about what that term really [0:36] means and sort of diving into that and [0:37] sort of being able to pick that apart so [0:40] our goals for today are to understand [0:42] what is FRP um how do we categorize [0:45] different things that sort of fall into [0:47] that umbrella and then how do we [0:49] evaluate those different things in a in [0:51] a nice way so um the way we're going to [0:55] get started learning these things is [0:57] start with sort of what I'm calling [0:59] first order F FRP and this is kind of [1:01] what Elm looks like and then we'll [1:02] Branch out into a bunch of different [1:04] variations that sort of build on that [1:07] core so first thing is first order FRP [1:11] so another way to think of that is like [1:13] how it works in Elm we'll see why it's [1:15] first order in a bit so the sort of key [1:18] thing to think about [1:19] is when we're dealing with FRP what we [1:22] really want to have is a way [1:24] to deal with interaction from the world [1:26] so we have a user over here and they're [1:28] typing and they're looking at things and [1:29] so we're getting sort of key presses [1:32] Mouse events um and bringing that into [1:35] some sort of processing system so in Elm [1:37] this is called the signal graph um in [1:39] JavaScript it might be just a tangle of [1:41] callbacks so sort of what F FRP is about [1:44] is rather than using callbacks and these [1:46] kind of ad hoc ways of structuring time [1:49] um we actually have a system that that [1:51] gives it explicit structure that you can [1:52] talk about in a in a more concise way um [1:55] and then we process these things and [1:57] send them back so we say to the computer [1:59] hey you should show this on screen or [2:01] you should send this HTTP request or [2:03] these various things so we get this core [2:05] of signal graphs [2:08] um so the key part of a signal graph is [2:11] that it has inputs from the world so [2:13] these are things like Mouse do position [2:16] um and if you look at the type of this [2:18] uh that colon means has type and it's [2:21] not just a pair of integers but it's a [2:22] signal of pairs of integers so it's a [2:26] mouse position that's changing over time [2:28] so as you move your mouse uh that value [2:30] is updating um another example of this [2:33] is keyboard. last press so this is the [2:35] last key that's been pressed down and [2:38] you can think of this the same way time [2:40] is passing and we're seeing the value [2:43] change over time so in this case I was [2:45] typing hello strange Loop and then I got [2:47] distracted and then I surfed the web for [2:50] a bit and then I came back at a at at a [2:52] later point so you're sort of seeing [2:54] this change over time and and having a [2:56] way to talk about that [2:58] explicitly um so from inputs uh we can [3:03] do a little bit that's sort of like the [3:05] raw information coming from the world um [3:07] but on top of that we want to be able to [3:08] transform those things um so with for [3:11] that we use this this function LT so [3:15] let's we take in a signal so we can take [3:17] in our key presses signal and then we [3:20] have a function that goes from as's to [3:22] B's so we take the the values of a and [3:24] we go make a signal of B so an example [3:27] of this would be saying we have this [3:29] function is consonant so we're checking [3:31] each of these letters true or false if [3:33] they're consonant and what we get out is [3:35] a new signal um where we say Okay H [3:38] that's a constant e that's not l l so on [3:41] and so forth so we can actually [3:43] transform these values as they're [3:44] changing over time and so here what [3:47] we're doing is we're really [3:49] handling uh a Time varying thing rather [3:52] than doing this a more like event based [3:54] callback kind of [3:56] system um the next thing we can deal [3:59] with is St so in Elm this is called fold [4:01] p and you can read this as fold from the [4:03] past it's meant to look just like fold [4:06] from the left fold from the right and [4:08] what's going on here is again let's take [4:09] our trusty key presses signal and when [4:14] we actually use fold P we're going to [4:16] give it a starting State this is this [4:18] guy and a way to update that state so [4:21] whenever a key press comes in I'm going [4:22] to say okay cool I got the key I got the [4:25] state that I'm working with and I [4:27] increment that by one so when we look at [4:29] the signal that we get out we count 1 2 [4:32] 3 4 5 so so on and so forth um and so [4:35] this is a very simple example of [4:36] carrying state but you can think of [4:38] something uh quite a bit more [4:40] complicated uh where perhaps you have a [4:43] uh to-do list application and you want [4:45] to model uh what your program is doing [4:48] as things change over [4:50] time finally we have a way to merge uh [4:54] signals together so the very basic one [4:57] is called merge which it takes two [4:58] signals and it just kind of [5:00] merges them together this this is one of [5:02] the well-named uh Primitives [5:06] um there's another one called lift two [5:09] questionably named primitive um this [5:12] takes two signals and at any point in [5:14] time it applies a function to both of [5:15] them and produces a new signal so this [5:18] is a way of saying if I have a signal of [5:19] x coordinates and a signal of Y [5:21] coordinates I can apply a function that [5:23] puts them together um and this becomes [5:25] useful when let's say you have window [5:27] dimensions Mouse position a whole bunch [5:30] of signals you can start to bring these [5:31] together um and so in addition to lift [5:33] two there's like lift lots of them lift [5:35] 3 4 5 6 7 8 it stops at [5:38] eight uh but you can you can go higher [5:40] through various tricks um so so what we [5:44] have is a way of talking about this [5:46] graph setting up an input from the world [5:49] uh transforming it updating some State [5:51] merging things together and this gives [5:53] us the tools to build this kind of [5:55] network this signal graph um and so if [5:58] you imagine a program like uh like a [6:01] to-do list like this um you can think of [6:07] this program as we model the inputs we [6:09] go update some State we transform that [6:11] into something to show on screen and [6:13] sort of we have this architecture for [6:15] our [6:17] application so that's sort of the [6:19] operational details that's kind of what [6:21] you would learn the the very abbreviated [6:23] version of what you would learn if you [6:25] if you dug into Elm um but it doesn't [6:27] really get at the sort of principles [6:28] that went into that design um so so when [6:31] you think about the core design you [6:33] start to see some details that aren't [6:35] apparent immediately so the first the [6:37] first one is that signals are connected [6:38] to the world this is a key property of [6:41] elm system that doesn't appear [6:42] everywhere else but you have direct [6:44] connection to Mouse position or [6:46] keyboard um signals are infinite this [6:49] one is subtle but it essentially means [6:51] that there's no such thing as deleting a [6:54] signal there's no create you can't have [6:56] an input that you then later throw away [6:59] this is like saying I have the mouse but [7:01] then I took the [7:03] mouse off of my laptop like it doesn't [7:07] like the the essentially it's saying the [7:09] inputs to your program are [7:11] fixed um another property is signal [7:14] graphs are static there's a known [7:16] structure um for your application from [7:18] startup all the way into the future and [7:20] this sounds like oh maybe it could be [7:22] dynamic we'll get to that there are tons [7:24] of tradeoffs that come with that so this [7:26] is actually a property that gives you [7:27] lots and lots of power that we'll get [7:28] into in a bit [7:30] um finally it's synchronous by default [7:32] um so what that means is when you type [7:34] hello um as someone who's observing your [7:37] application like you want them to those [7:39] letters to show up in that order and so [7:42] when you have branching structure like [7:43] this you need to be sure that those [7:45] events flow through and maintain order [7:46] so an Elm that's maintained by default [7:49] uh that property isn't shared by all [7:53] variations so okay what do we get what [7:55] do we get when we make these design [7:57] choices um so the first one is [8:00] efficiency um this one it doesn't see it [8:03] does it's not clear at this point why [8:04] I'm saying this but essentially what's [8:07] happening is everything's event driven [8:09] right an event comes the world it flows [8:11] through our graph it may get send back [8:13] as a update um and with our stateful [8:16] nodes we need to look back at the [8:18] previous state and that's it so we'll [8:21] see why it's necessary to point out that [8:23] efficiency is a property that we have um [8:26] another thing is architecture so this is [8:28] a [8:30] uh Elm program that's kind of like the [8:32] typical [8:36] um [8:37] uh example of a MVC style application um [8:42] and this is just hooking up to the world [8:44] and so you can see it's actually using [8:48] um local storage so if I reload this [8:52] page the changes propagate over because [8:55] I'm saving it um so which is to say uh [8:59] uh interacting with the world becomes [9:01] quite simple now when we look at the [9:03] structure of this application it breaks [9:05] up nicely into four parts as every Elm [9:08] program will it breaks up into a model [9:11] so we that's that's tiny um is that [9:15] better is better [9:18] is okay we we just want to overview here [9:21] we don't need to see the details exactly [9:23] but we first have a model so we just are [9:25] saying here's the state of my [9:27] application I have a list of tasks what [9:28] is a task it's a thing with a [9:30] description whether it's completed [9:31] whether I'm editing so we're explicitly [9:33] modeling what our application means um [9:37] and from there we have update how do I [9:40] take that state and Step It Forward [9:42] based on stuff people want to do so [9:43] maybe we want to add a task or delete a [9:45] task um and finally we have a view which [9:49] is how do I take the state of my [9:50] application and turn it into HTML that I [9:53] can show on screen and what's nice here [9:55] is we can make sort of uh reusable [9:59] functions so I can say how do I show a [10:02] task on screen and then I can say map [10:04] that over all of my visible tasks so I [10:07] can start to build up UI in a really [10:09] nice declarative way with with these [10:11] pure functions so that architecture that [10:15] structure comes from the static s graphs [10:18] they guide you towards writing your [10:20] applications that way um I consider that [10:22] a really big benefit um another thing [10:25] that you get is hot swapping so you guys [10:29] may have have seen the Mario program [10:31] from last year um if you didn't it's a [10:34] good way to learn Elm in like that isn't [10:36] only like 10 minutes um so notice I [10:40] walked him over to the side of the [10:41] screen and I'm going to change gravity a [10:44] little bit and now jump him and then [10:47] change it again and suddenly gravity is [10:50] stronger so we're able to change the [10:51] behavior of the program while the [10:53] program is running and sort of see those [10:55] changes propagate uh automatically so [10:59] the final thing that we get well not the [11:02] final thing but like the other thing [11:03] that I felt necessary to share with you [11:05] guys today um is the time travel [11:07] debugger um so you may have seen this [11:09] online but we've got a new version [11:12] coming out that's easy to install and so [11:13] I just wanted to do a tiny demo of [11:17] that um so here's that trusty Mario [11:20] program and we've got like Mega Mario ah [11:24] man I changed the code by accident last [11:26] night and forgot to change it back but [11:28] so we can see where me Mario was before [11:31] um and we can pause this program and [11:33] sort of go back in time to wherever we [11:36] want to go and so say we go like near [11:38] the beginning and we pick up where we [11:41] where we started there so we say okay I [11:42] didn't want to walk to the left I wanted [11:43] to walk to the [11:45] right and so one cool thing here is that [11:48] we've got we can look at data in our [11:50] program so if I zoom if I go back a [11:52] little bit you can see the arrows value [11:55] I'm now pressing right and if I go back [11:58] a little more I'm pressing up right now [12:01] and then I stop pressing up um so you [12:04] can start to get really cool insights [12:05] about what's going on your at your [12:07] program at particular points in time um [12:09] and what's neat about this you can oh [12:12] yeah there's another thing that's neat [12:13] about it that I forgot to tell you [12:15] um okay so you see him jumping [12:19] around uh okay so here's the code and [12:21] again it's got model update display [12:25] these parts oh jeez what's going on here [12:27] okay and I want to change [12:29] what do I want to change I want to [12:30] change gravity [12:32] again so when I save that and this is in [12:34] Sublime Text those changes again [12:36] propagate but they Al They propagate for [12:38] the whole history so we can see that now [12:40] mari's can jump like way too high so [12:43] we're like that's that's no good we want [12:44] Super Mario to have like a reasonable [12:46] kind of [12:48] jump okay so now we've got Super Mario [12:50] with good [12:52] jumps Okay so [13:00] so these are kind of like the core [13:01] things that come from having a static [13:03] signal graph a lot of these abilities to [13:06] swap and code and Replay code comes from [13:08] the fact that we know what if we know [13:09] what the program looks like at the start [13:11] we know what it's going to look like in [13:13] an hour in a day um and we don't have to [13:15] do some kind of crazy complex analysis [13:17] to to figure that out or to match things [13:20] up so that's sort of an overview of what [13:23] first order FRP is like um but the [13:26] question you may have in the back of [13:27] your head you know what what happens if [13:29] signal grass could be reconfigured what [13:31] is higher order FRP surely higher is [13:34] better [13:35] um so this is the question that sort of [13:38] guides a lot of the thinking onp and a [13:41] lot of the research um because it's a [13:43] very very surprisingly hard question um [13:46] so there are a couple ways that it has [13:48] been pursued we're going to explore them [13:50] one by one and they sort of fall into [13:51] sort of nice separate categories um so [13:55] the let's let's dive in so the first one [13:58] is higher order F FRP so this is really [14:00] just saying okay we took this core [14:02] design and we don't like that signal [14:05] graphs are static we want to get rid of [14:07] that constraint so what do we need to [14:09] add what do we lose um so one thing I [14:12] should point out is that with all of [14:14] these variations it's not better or [14:16] worse it's trades right whenever you're [14:18] making this decision it's like I'm going [14:19] to get this feature and I'm going to [14:21] lose this other one so what happens when [14:23] we make we get rid of this core design [14:26] that zass are static so typically this [14:29] is with a function called join um so [14:33] it's not super clear exactly what that [14:34] might mean but here we have a signal of [14:36] signals um again it's not super clear [14:39] what that might mean um [14:41] but uh you can think of it as I have a [14:45] group of signals and I'm picking which [14:46] one to listen to right now so you can [14:48] think of this signal as the guy coming [14:51] out here and this signal as like [14:53] choosing one of these so as I get [14:56] updates I listen to a different [14:59] uh signal coming in so that seems that [15:02] seems straightforward right it sounds [15:04] sounds plausible so already at this [15:07] point we have some questions so what [15:09] happens to these signals that aren't in [15:11] use right now right no one's listening [15:14] so should it keep running should it [15:17] pause this is a surprisingly difficult [15:19] question now you get into even crazier [15:21] issues when you start adding signals [15:23] that hadn't previously been observed um [15:26] and you can keep adding these and adding [15:28] these so to make this a bit more [15:30] concrete let's see an example where we [15:32] see the bad behavior here so here we [15:34] have a very simple uh signal it's [15:37] counting Mouse click so I click it [15:39] increments I click it in increments um [15:42] and let's see something a little bit [15:44] more complicated so here uh we've got [15:47] something that's either counting Mouse [15:49] clicks or it's zero so if I put true in [15:52] I'm going to get count Mouse clicks if I [15:54] get put false in I'm going to get a [15:55] constant signal of [15:57] zero so [15:59] let's say we start with true and I click [16:01] five times so I should be at five now I [16:04] go false it's zero and I click five more [16:07] times and I go true again okay what [16:10] should uh the value be of my signal [16:13] should it be zero because I'm restarting [16:15] should it be five because I'm picking [16:17] off where I left off should it be 10 [16:19] because that's the total number of [16:21] clicks that have happened over the [16:22] course of this application [16:25] now because a lot of This research [16:27] happens in purely functional languages [16:30] or or or languages where we want to have [16:32] properties about uh equational reasoning [16:35] um our answer is uh chosen for us so if [16:39] you look at this code count mous clicks [16:41] and this code count Mass clicks they're [16:43] the same expression so it should be [16:46] possible to to [16:48] replace uh this variable with click [16:51] count and have the same behavior so [16:53] essentially our behavior is chosen for [16:54] us when we get to that point where we go [16:57] true again it needs to be at it needs to [16:59] be the count of the full number of [17:02] clicks so where this starts to get crazy [17:05] is let's say no one defined that but [17:07] they wrote this [17:09] program um so I start my program this [17:13] signal is false so it's 0 I wait 10 [17:16] minutes I wait 20 minutes and I'm [17:18] clicking away like a maniac and then [17:20] suddenly I switch to count Mouse clicks [17:22] this is a signal that's never existed [17:23] before but I know that it needs to keep [17:26] track of all of those past clicks [17:29] so I'm in a place where I don't [17:31] necessarily know that I'm going to need [17:32] that signal I know that it needs to use [17:35] all past history so I'm in a bad [17:38] situation essentially the problem is [17:40] creating a new signal may need infinite [17:42] look back I may need to look back at all [17:44] the previous events to be able to [17:46] reconstruct the signal so that I have [17:48] these properties that I want um another [17:50] way to say this is memory growth is [17:52] linear with time right so every click [17:54] that happens every time step that [17:56] happens I need to keep track of that in [17:57] case uh you have to look back um the [18:01] solution isn't that this is a bad idea [18:03] it's that we only should switch to [18:05] signals that have safe amounts of [18:06] History right it's not that switching is [18:08] necessarily bad but that particular kind [18:12] of switching definitely causes problems [18:14] so what this is turned into is how do we [18:17] restrict the definition of join with [18:19] fancier type so how do we write a way to [18:21] switch between things that rules out the [18:24] possibility of this infinite look back [18:26] so we can implement it efficiently and [18:28] this is where a lot of the research has [18:30] gone on this topic um and when you see a [18:32] library a modern library in hasell or [18:36] wherever else that is in this category [18:38] of higher order FRP it's going to be [18:41] doing a trick like this so one cool [18:43] example of it is this paper high order [18:45] functional reactor programming in [18:46] bounded [18:47] space the names get very long for these [18:49] papers when your topic is functional [18:51] reactor programming [18:53] um so he takes a quite sophisticated [18:57] approach where he uses a thing called [18:59] linear types and this is a kind of type [19:01] system that doesn't appear in hll or a [19:03] camel or agda or I don't know if it [19:06] appears in it I heard it came out [19:09] in interest the point is that like this [19:10] is a this is a rare type system to see [19:13] in in languages that you can go and try [19:15] out um but he's able to show that you [19:18] can't write a program that looks back [19:20] unless you specifically say you want to [19:21] save that information [19:24] so we can write programs like this but [19:26] we're making some tradeoffs so we got [19:28] got rid of the idea that signal GRS are [19:30] static so we're losing the the that hot [19:33] swapping is really easy we're losing [19:34] that time travel debugging is [19:36] straightforward we're losing that [19:37] architecture is really straightforward [19:40] um and we may be dealing with a slightly [19:42] more complex API or perhaps a more [19:44] complex type system um so when thinking [19:49] about this one [19:52] uh let's say let's say there weren't any [19:54] trade-offs here let's say this was like [19:56] the the pro the problems were solved [19:58] let's look back at the [20:01] um the architecture of this to-do MVC [20:04] thing from before so when I look at this [20:08] code and let's say I could switch on [20:09] signals would I want to architect [20:13] architect my application that way like [20:15] given the ability would I use it um and [20:17] so maybe that would mean that instead of [20:20] a state that I model like this it has [20:23] the tasks isn't just a list of tasks [20:26] it's a list of signals of tasks and we [20:28] doing some more [20:30] complicated um signal signal structure [20:33] with that and I'm not sure that's a [20:35] better way um I think that's something [20:37] that uh I'd like to see some examples [20:41] and that could be that could be quite [20:42] interesting but I'm not I'm not totally [20:44] sold yet um so next we have a thing [20:48] called asynchronous data flow so if you [20:51] know reactive extensions reactive Coco [20:53] bacon JS essentially if you have if you [20:56] see something that looks like FRP and [20:58] it's in uh an imperative language it's [21:01] probably in this category of things so [21:04] again we can look at our core design and [21:05] see which things we cross off so of [21:08] course we cross off that singal grass [21:09] are static because who yeah we don't [21:12] want that um we cross off signal gra [21:16] that signals are infinite so this is [21:18] another way of saying that uh signals [21:20] can end like I can start it up it can [21:23] trigger four events and then it can die [21:26] um finally we get rid of [21:29] uh the design that it's synchronous by [21:31] default this doesn't mean you can't have [21:32] synchrony uh but you have to do it [21:34] yourself um that it's asynchronous is a [21:37] is a hint that that's the that's one [21:39] thing we're getting rid of so uh these [21:42] guys use uh something called flatten it [21:45] may look very familiar um when you're [21:48] writing an imperative language you get [21:50] the you get this extra ability of naming [21:53] things in a way that is clear for people [21:56] so flattening signals of signals it's [21:59] it's quite it's it's quite intuitive to [22:01] name it that way um so but this is the [22:04] same idea and it ends up looking a [22:06] little bit different because we've given [22:07] up on more of these Core Design points [22:11] uh I shouldn't say given up we've [22:12] decided to shoot for a different point [22:14] in the design space so if we look back [22:16] at that program from before where we [22:18] were like okay count Mouse clicks needs [22:20] to be equal to count Mouse clicks we get [22:21] all these problems okay now we're in an [22:24] imperative language and no one ever said [22:26] you were going to have that guarantee so [22:28] whenever you create one of these signals [22:29] you're creating a totally new one um and [22:32] what that means is when I do clicks or [22:35] zero I start with true I count 1 2 3 4 5 [22:40] I go to false so it's zero and then I [22:42] switch back to true and I count 1 2 3 4 [22:44] 5 I start at zero again it's an entirely [22:46] new signal so we're creating new things [22:48] um and there's some mechanism of [22:50] cleaning up garbage as we go um we still [22:54] have that question from before of what [22:56] happens when no one's listening to a [22:58] signal [22:59] right you may have created a signal and [23:00] no one's paying attention so some [23:03] variations of this style um [23:06] have uh have a thing called hot and cold [23:09] signals so reactive extensions in [23:11] particular has hot and cold signals [23:13] meaning if I have a signal and no one's [23:17] listening there's a joke here um if I [23:19] have a signal and no one's listening um [23:22] what should H what should happen and if [23:23] it's hot it's going to keep producing [23:25] things and if it's cold it's going to [23:27] stop um [23:30] okay so finally we have arised FR FRP so [23:35] this is something you can see in has [23:37] with a couple libraries Yampa is sort of [23:39] the first the first one that came out [23:41] netwire is a I think more modern version [23:44] um that that looks quite interesting um [23:46] Elm has a thing called automaton which [23:48] is the same concept so when we look at [23:51] this again we code to our ctis we cross [23:53] off that s grass are static the whole [23:54] point of this is to try to get rid of [23:56] that constraint now in this one we cross [23:58] off that cographs are connected to the [24:00] world um this is a very clever and [24:04] interesting choice that has has some [24:06] cool effects so we'll get into this by [24:09] looking at how this Library Works in Elm [24:12] um so for the basic API we've got this [24:15] function called Pure and this is [24:17] creating an automaton so you can think [24:19] of an automaton is a little robot who [24:20] takes things in and gives you things on [24:22] the other side so I can think of a pure [24:24] automaton is that increments by one you [24:27] give it one it gives you two you give it [24:29] 42 it gives you 43 you give it 100 gives [24:31] you 101 I think I got this right um and [24:36] so here is how we actually write that [24:38] out right we have a function it takes in [24:40] an input and it increments it by [24:42] one next we have a way to model state so [24:46] this is quite like a fold P um this [24:49] one's nicer to see by example so with [24:51] the state we say here I have my base [24:54] value so in my brain I have [24:56] zero um and then as my step function [25:00] whenever I get in a value I'm going to [25:02] say cool that's a nice value you gave me [25:04] and I'm going to take my total and [25:06] increment it by one so someone gives me [25:09] the string hello and I say oh I got a [25:10] thing one and then I give out one and [25:13] someone says how are you and I say okay [25:15] two I got two messages um and so here we [25:19] can model state in the same way um the [25:21] last thing that I want to show as as [25:23] part of the basic API is how we chain [25:25] these together so when you have two [25:27] little automat who are processing [25:29] information you can put them next to [25:30] each other so this is saying I have one [25:33] guy that gives takes A's and gives B's [25:35] one guy that takes B's and gives C's and [25:37] I can just put them next to each other [25:39] so if we wanted to say plus two we can [25:41] just chain uh plus one and plus one so [25:45] one two two three three um and process [25:49] that [25:50] information [25:52] so this gets interesting when you want [25:54] to start switching on things so you can [25:57] think of each of these as a little [25:58] automaton who has some State and this [26:00] one these are all counting so their [26:02] state starts at [26:04] zero um and we're going to put them into [26:07] our sort of active position so values [26:10] will be flowing through here so if we [26:11] start out we know this guy's zero and [26:14] then we pass a couple events through [26:15] let's say five events go through so [26:17] increments 1 2 3 4 5 then we switch it [26:20] out for the green one now at this point [26:22] this guy is holding five this guy's [26:24] holding zero this guy's holding zero [26:26] What's really clever here is that these [26:29] nodes aren't connected to the world so [26:30] when we take it out of the graph this [26:32] question of what happens does it keep [26:35] running it just answered uh it doesn't [26:37] keep running it's not connected to [26:39] anything um [26:41] and what this means is we can switch [26:44] things in a in a more creative way so [26:46] let's say I give this three events 1 2 [26:48] three and then I switch again so now [26:51] I've got five in here three in here zero [26:54] and we can sort of switch this in really [26:57] interesting ways [26:59] so this is kind of the the key Insight [27:01] of aoz FRP but we gave up something [27:03] really important which is that we can [27:05] talk about uh signals coming from the [27:08] world so in this system there's no way [27:11] to talk about Mouse position that's [27:13] something that exists externally so what [27:15] that means is aoiz FRP is really about [27:18] structuring code and so it can show up [27:21] in a library for Elm or H or whatever um [27:25] and it's in competition with like using [27:28] pure functions and modules as a means of [27:31] abstraction so the question ask yourself [27:33] here is for my particular domain is this [27:37] way of structuring code giving me [27:39] benefits and I know there's been success [27:42] with music and uh with netwire there may [27:47] be some other cool things of structuring [27:49] your code um I personally haven't used [27:52] it a lot but uh it's compatible with all [27:55] of these different approaches [27:59] so when we zoom out um you notice [28:03] I I put the circle around Elm to have [28:07] all three colors indicating that this [28:10] isn't a competition of like which one's [28:13] better but rather different points in a [28:15] design space that are complimentary so [28:18] when we think of how this connects to [28:20] Elm how does high order FRP connect I [28:22] mean this is really adding a feature um [28:25] this is something that could be done I I [28:27] personally don't want to take that step [28:28] and give up these the properties that [28:31] Elm is able to get if we find a way to [28:33] do it that's nice that's totally [28:35] possible to add to Elm with asynchronous [28:37] data flow here you're giving up the [28:40] synchrony uh uh guarantee so this isn't [28:44] something you'd embed in Elm it's [28:45] something that you'd embed Elm in so [28:48] let's say you're prey who who employs me [28:51] and you're thinking of using Elm in your [28:53] system you'd like to make a little part [28:54] of your application what you can do is [28:57] make that part an elm and hook up to it [28:59] with bacon JS or uh RX or whatever and [29:03] have that interactivity so you can have [29:05] all these nice guarantees when you want [29:06] but still integrate with a system that [29:08] has something more complex going on [29:11] finally arrowed FR FRP it can just be a [29:13] library it is a library um and so when [29:17] we think of the broad categories here um [29:20] we can think of one uh as static graphs [29:23] um graphs that don't change over time [29:26] and the other category as dynamic graphs [29:28] graphs that do change over time and [29:29] these are the sort of broad connecting [29:32] categories and then for distinguishing [29:34] these bottom three you can look are the [29:36] signal are there infinite signals [29:38] another way to tell this is like is it [29:39] in hasal it's probably in this category [29:43] um finite event streams um so this is [29:46] signals that can end um an easier way to [29:49] tell this is like is it in an imperative [29:51] language um and finally neither in this [29:55] world we don't explicitly talk about [29:56] signals or whether whether they're [29:58] finite or infinite or [30:00] not so this kind of gives us [30:03] our taxonomy or like different points in [30:06] the design space so let's sort of try to [30:09] pick out the the the lessons we can [30:12] learn from this so I know I sort of [30:15] introduced parts of elm really really [30:16] quickly I didn't do a lot of examples [30:18] you can learn more at this site um about [30:21] Elm in particular if you want to learn [30:23] more about the history of FRP [30:25] specifically first order FRP and higher [30:28] order FRP this um this is my thesis and [30:31] it has a section on history of FRP and [30:34] if you look at the citations you'll just [30:36] see links to all the papers that had [30:38] been published by that by the time this [30:41] was was written um when it comes to [30:44] categorizing things [30:46] [Music] [30:47] the it means I'm running low on time um [30:51] when it comes to categorizing the [30:52] questions you want to be asking are is [30:53] it synchronous by default right am I [30:56] getting these ordering guarantees where [30:58] know that if someone types hello and [31:00] clicks a button that we're going to get [31:02] it in that order and not the button [31:04] click happens halfway through cuz the [31:05] processing happens too takes too long or [31:09] something um does it allow a synchrony [31:12] so it's not just that you want synchrony [31:14] like that's a nice default but you know [31:16] there are times when you don't want to [31:17] block everything so you need to make [31:18] HTTP requests you need to make uh rights [31:21] to disk um and so Elm builds in this [31:24] idea of asynchrony in a nice way where [31:26] you still get the default of synchrony [31:28] but when you don't want it you can [31:29] explicitly say um and I not all systems [31:34] that are out there have a have a plan [31:36] for how asynchrony will work um so [31:39] that's something that you should keep in [31:40] mind especially when you're looking at [31:41] higher order [31:42] FRP um can I talk about inputs so this [31:46] is a essentially a way of saying like is [31:47] it is it a kind of awise system is it a [31:49] way of structuring code or is it a way [31:51] of talking about [31:52] events um can I reconfigure My Graph um [31:57] and when you want to evaluate this the [31:59] question isn't how can I get the [32:02] fanciest like words on my on my library [32:05] it's what properties do I need for my [32:07] application right so when you're looking [32:10] at something like reactive extensions [32:12] and you want to write some server code [32:14] where I've got 10 people connecting to [32:16] my server they all want some information [32:19] there asynchronous by default is great [32:21] like why why should any of them block [32:23] depending on the order that things came [32:25] in or if you're dealing with some system [32:27] where it's just not that important to [32:29] have this synchron that's that will fit [32:32] you really well if you want to have the [32:34] kind of debugging tools that Elm has you [32:36] may want to make a different Choice um [32:40] yeah what is debugging like uh this is a [32:41] question I can't answer for uh the [32:45] higher the the sort of dynamic graph [32:47] ones as easily um but it's something to [32:49] think about when you're starting out is [32:51] like this isn't just a cool thing to [32:53] start with you have to carry this [32:55] through for the whole life of your [32:57] application and finally does the code [32:59] come out nice [33:02] so there may be something where you you [33:04] like it and then once you write a lot of [33:07] code things start to feel more [33:10] complicated than you thought right so [33:12] this is particularly important when [33:14] you're talking about the asynchronous uh [33:16] data flow kind of things um you can get [33:19] more features but you're paying a [33:21] complexity cost for that and do you need [33:23] that in your application is it something [33:25] that you can live without to get some [33:27] more simp [33:30] so that's sort of the space of [33:31] functional reactor programming hopefully [33:33] I've sort of given you some ways of sort [33:36] of categorizing picking them out uh and [33:38] things to explore um if you're [33:40] interested in learning more about FRP um [33:42] if you learn Elm then it's easy to go to [33:45] these other ones and you learn these [33:47] principles which you can use there um [33:49] but yeah so hopefully this has given you [33:51] some insight uh thank you very much [34:01] so we've got some time for questions I [34:04] think yeah so regarding the Jo operation [34:09] yes and the argument that you need to [34:12] look back in time and have all all [34:14] possible values that have passed through [34:15] the signal previously in order to [34:18] maintain refer [34:19] transparency um is there a reason you [34:21] don't just model signals as [34:24] effectful then done with it [34:30] yeah so that's the switch of going to um [34:34] the sort of asynchronous data flow style [34:37] um oh so the question was why why with [34:40] the join operator in higher order FRP um [34:44] why keep that constraint where count [34:47] Mouse clicks should be equal to count [34:48] Mouse clicks why not just say hey that's [34:50] not how signals work [34:53] um I can't say for sure if there are [34:55] libraries that do that um this is this [34:57] is a design choice you can make you get [34:59] different trade-offs doing that that [35:00] kind of thing um that would be more in [35:02] the ballpark of the sort of reactive [35:04] extensions bacon JS style [35:08] though any [35:10] other okay [35:14] yes okay [35:19] yeah when it yeah is it doing that like [35:23] once you've already like if you're doing [35:24] it in Elm and you wire some actual [35:28] signals uh does it do the [35:31] switching uh after it's after everything [35:34] is running and if so how does that kind [35:37] of switching make that tradeoff of of [35:42] when it starts getting the values and [35:43] all that yeah so the question is like [35:46] when do you hook up an arsed FRP system [35:48] and what happens once you do hook it up [35:51] um so typically you'll Define your aized [35:54] FRP system and design that dynamism into [35:57] it and and then hook it up and those [35:59] sort of flow through and things will be [36:01] switched in and out um when it's hooked [36:04] up isn't isn't such a huge deal in that [36:08] you can give it values at any time and [36:10] it'll step forward its sort of world so [36:14] I'm not sure if this is answering your [36:15] question very well but [36:18] is yeah yeah so when you step in aaton [36:22] forward what you're really getting is it [36:24] the value it produces and the new [36:26] automaton that you should run [36:28] um so you sort of are like forking [36:29] reality and say here's one where I've [36:31] stepped the world forward and and I [36:32] still have that value I started [36:36] with uh uh yes yeah [36:40] so part of what we were trying to [36:45] Doc [36:50] separates how would [36:53] take the editor for a single line ofas [36:56] the do and includ it in another [36:59] application right so the question is [37:01] about how do you get a kind of [37:03] modularity in a in a in an application [37:05] like the to-do list is that fair y um so [37:09] I think that there are a couple ways [37:11] right so one is to use the higher order [37:13] style uh something like AED FR FRP one [37:16] of these reconfigurable ones to give you [37:18] that kind of abstraction you can wrap it [37:20] in that way what I my opinion is that [37:24] this is something we should Explore More [37:26] in Elm what I'm advoc [37:28] right now [37:30] oops um is sticking with module system [37:34] and functions so if you want to [37:36] modularize your tasks you'd make a [37:38] module that is all about tasks what's [37:40] the model of them how do I update how do [37:42] I view them and then I make a module [37:44] that's all about the actual to-do list [37:46] what's the model how do I update it how [37:48] do I view it and I can keep doing this [37:50] sort of like uh nesting architecture uh [37:53] to get that kind of modularity and it's [37:55] not clear to me that one way is going to [37:58] be superior to the other and I think [37:59] there hasn't been enough exploration to [38:01] know like this is the right way or even [38:04] if there is a right way for all [38:08] cases yeah [38:12] uh yes have you looked [38:15] at at all um I haven't I [38:19] haven't they [38:25] oper okay I'll take a look what your [38:28] thoughts were yeah I haven't seen it I [38:29] haven't seen [38:31] it [38:36] yes yeah so that is terminology that I I [38:40] think is a bit confusing so we talked oh [38:43] sorry the question is how does push vers [38:45] pull fit into this this taxonomy um [38:50] so with the asynchronous data flow we [38:53] talked about hot and cold sort of what [38:55] happens when no one's listening what [38:56] push and pull is about is uh with Elm [39:00] it's event driven so meaning it's push [39:02] you push an update to it and that [39:03] propagates through what pull means is [39:06] someone at the bottom says Hey I want to [39:07] see something and so that goes through [39:09] your graph and pulls values in sampling [39:12] from whatever that time point is my the [39:15] argument we've made in in the work we've [39:17] done on FRP is that uh a push system can [39:20] model a pull one in that when you get to [39:24] the bottom of your [39:25] program one way to say it is I pull the [39:28] values through another way to say it is [39:30] I trigger a push through um and so I [39:33] don't think there's as huge a difference [39:35] between this [39:36] as as the the distinction isn't as [39:39] important as it [39:43] sounds um [39:50] yes say it [39:52] again how [39:54] do deal a producer [39:58] okay so the question is about how do you [39:59] deal with um sort of like too much data [40:02] to process through your graph right is [40:03] there a way to drop things or so in the [40:07] domain of um [40:10] UI uh you're dealing with human input so [40:13] you're not going to be getting in that [40:14] situation very often so a lot of these [40:17] systems are prioritized towards use [40:19] cases like that um and I think in so [40:24] essentially the subtext here is like [40:26] what is the like uh push back like [40:30] mechanism um I [40:32] think I'm I'm not an expert on this [40:35] particular question there may be F FRP [40:37] systems that do it Elm is primarily [40:40] concerned with UI so it's not the major [40:43] uh [40:45] Focus um I've gone over time a little [40:47] bit um let's chat outside if you have [40:50] questions thank you very much