bartblast

bartblast

Creator of Hologram

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

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

bartblast

Creator of Hologram

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

bartblast

Creator of Hologram

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

bartblast

Creator of Hologram

Hi @venkatd, thanks for the feedback! :slight_smile:

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

bartblast

Creator of Hologram

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!

Where Next?

Popular in Questions Top

LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
Kagamiiiii
Student &amp; New to elixir. Nice language. I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
makeitrein
Hey all, just started picking up Elixir last week and am writing a scraper as a learning project. Baby step #1 is extracting the number ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New

We're in Beta

About us Mission Statement