bartblast
Signals, computed properties and other reactivity patterns in Hologram
This discussion started in the Does Hologram support two way data binding? thread and evolved into a deeper conversation about reactivity patterns in Hologram. We’re exploring questions like: Should Hologram support computed properties? How do signals fit with functional programming? What’s the right abstraction for derived values?
Most Liked
garrison
I haven’t replied again out of concern for this specifically. I want to make sure I’ve seen the API you have in mind before I give further thoughts. Plus it will probably be easier to explain what I have in mind if I can demonstrate it in terms of what you already have.
BTW, your dedication to responding to feedback on here is amazing. Don’t think it goes unnoticed!
bartblast
Signals are compelling, but they’re fundamentally a JavaScript reactivity primitive. Hologram transpiles Elixir to JS, preserving Elixir’s functional semantics - immutable data structures, explicit state updates via put_state, etc. Adopting signals would mean either reimplementing them in Elixir semantics (complex) or breaking from Elixir patterns (defeats the purpose). The current explicit approach aligns better with Elixir’s philosophy.
That said, a middle ground with similar purpose is planned - computed values/properties. This would give you automatic recalculation of derived state when dependencies change, similar to Vue’s computed properties, while maintaining the explicit, declarative, functional style that fits naturally with Elixir.
bartblast
When I said “middle ground” I didn’t mean “half-measure” - that was unfortunate wording on my part. I’m talking about reaching similar goals - automatic recalculation, performance optimization - but in a way that aligns with functional programming principles. I think computed properties actually fit naturally into the functional paradigm: they’re pure functions that transform immutable state into derived values. That’s fundamentally functional.
I know you’re not a fan of templates, but computed properties actually strengthen the template-as-presentation-layer approach by letting you move business logic out of templates into small, focused, testable functions.
To be clear: when I talk about computed/derived properties, I mean values like full_name, valid?, or formatted_price that derive from existing state - not template fragments. (Partials like <my_partial(param_1, @param2) /> would be a separate feature for generating template fragments.) These are tools in your toolbelt, not fundamental architectural commitments.
I think it’s worth considering the concrete benefits they provide - they solve real problems:
- Performance: Automatic memoization of expensive calculations
- Consistency: Derived state that can’t get out of sync when multiple values depend on each other
- Readability: Clear separation between data and derived values
- Testability: Small, pure functions that can be tested in isolation
Regarding “there is no forest; you are only concerned with the trees” - I understand your concern about losing the holistic view. But I think the key difference is that computed properties in Hologram would be values derived from state, not a reactive programming model that forces you to think in terms of dependencies and updates. You still write your component declaratively as a whole - the state updates explicitly through actions, and computed properties are just transformations of that state. The component’s logic remains interconnected and whole. Computed properties are simply a way to extract and name derived values rather than calculating them inline in the template.
Additionally, when components are small and properly isolated with focused responsibilities, it becomes easier to keep the entire component in your head at once - the “trees” are manageable and the “forest” emerges from how components compose together.
The key point: computed properties in Hologram would be optional and complementary to the existing explicit approach, not a replacement. They’re a tool for specific use cases where the benefits are clear. I want to be pragmatic about giving developers useful tools while maintaining a coherent architecture.
bartblast
Hi @venkatd, thanks for the feedback! ![]()
I completely understand your wariness based on KnockoutJS/EmberJS - those frameworks created convoluted webs of interdependent computations that were hard to trace.
However, I think those issues stemmed from their mutation-based reactive model: mutable observables, dependency graphs spanning multiple files/components, scattered observers with side effects, and hard to trace execution flow when changes cascade through dependencies.
Hologram’s approach is fundamentally different:
- Immutable state with explicit updates
- Dependency graphs scoped to component state and props only - local and bounded
- Pure functions, no scattered side effects
- Clear data flow: action → state update → re-render
Yes, there would be a dependency graph for computed properties, but it’s deterministic, local to the component, and traceable by reading the code. No hidden global reactivity. This explicit nature would even enable a Time Travel Debugger to track all actions, commands, and the computed properties graph in real-time.
Computed properties would be named, memoized pure functions that transform state. They’re still part of ui = fn(state) - the function is just composed of smaller, named, cached transformations.
Why memoization matters in ui = fn(state):
The key is that some form of memoization is needed anyway. Without it, you face: repeated function calls with identical inputs, expensive recalculations on every render, composed calculations where all functions in the chain recalculate even when inputs haven’t changed. Your options are: recalculate everything (expensive), manually cache (boilerplate), or automatic memoization. Computed properties provide the latter while keeping the functional paradigm intact, plus they help move business logic out of templates into named, testable functions.
Computed properties would be completely optional - just a tool in your toolbelt for cases where they provide clear value.
Does this address your concern? I know the term “computed properties” carries baggage from various JS frameworks, but I think the fundamental paradigm difference (immutable, functional, local) makes them a different beast entirely. Would love to hear your thoughts on solving these memoization challenges, or if different terminology would help clarify the distinction.
bartblast
Alright, here are some ideas to show what I have in mind! @garrison @venkatd
The core idea: a simple, component-scoped DSL that defines how derived/computed/memoized values (terminology is up for discussion) are calculated. Hologram automatically updates these values when any dependencies change, and the values are injected into template vars accessible with the @ syntax, e.g. {@my_derived_value}.
The dependencies for derived values can be state, props, and other derived values, so a dependency graph is built underneath and topological sort is applied to manage the recalculation order.
The DSL can be implemented in different ways (full_name probably isn’t the best real-world use case, but it’s easy to understand):
Option 1: Macro with explicit deps
derived :full_name, [:first_name, :last_name] do
"#{first_name} #{last_name}"
end
Option 2: Attribute annotation
@derived deps: [:first_name, :last_name]
def full_name(vars) do
"#{vars.first_name} #{vars.last_name}"
end
Option 3: Function-like syntax
defderived full_name(first_name, last_name) do
"#{first_name} #{last_name}"
end
…and other permutations and hybrids of these approaches.
Key characteristics:
- Pure functions transforming immutable state (no side effects)
- Component-scoped dependencies (local and bounded)
- Automatically memoized (recalculated only when dependencies change)
- Deterministic dependency graph (fully traceable)
- Still fits the
ui = fn(state)paradigm
This is fundamentally different from the mutable observer patterns in KnockoutJS/EmberJS that created convoluted webs of interdependencies. There’s no global reactivity, no scattered side effects, no mutation-based cascading updates.
Important clarification: This is purely for deriving/computing values without any component struct modifications. For reactive state updates, effects/watchers would be a separate concept – but that’s a different discussion.
Would love to hear which syntax option resonates with you, and what other ideas you might have!







