<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="335233" data-post-id="335233">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="josevalim" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/120/1787_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  josevalim
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Elixir</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>If that’s true, then starting with the higher level APIs of how these approaches differ is probably more relevant than the low level implementation details, because then there may be a better option than both mentioned here. At the low level, I think the streams trade-offs are hard to beat <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="335233" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/possible-payload-size-improvement-to-heex-list-comprehensions/64986/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-335233" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="335233"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #11"></div>
  </section>
</div>
    <div class="postbit" id="335237" data-post-id="335237">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="LostKobrakai" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/LostKobrakai/120/3072_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  LostKobrakai
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>There is quite a good example in LV: <code>Phoenix.Component.inputs_for</code>. The form abstractions in place right now don’t tell you which items in a list of assocs/embeds changed. The result of those abstractions just give you a new list. Currently it’s plain unoptimized, rerendering all the inputs of all the nested forms. If there would be a way to provide a key for individual items there could be optimizations, where unchanged items in the list don’t need to send data or rerender.</p>
<p>I don’t really understand how this is such a controversial topic given it’s exactly how react, solid, vue, … handle the issue of preventing unnecessary rerenders. At least in the current implemenation I don’t see streams are a replacement for that. Streams only work if you can push the detection of which record changed upstream. They don’t help in detecting which records of a list changed, which is what this topic is about by my understanding.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="335237" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/possible-payload-size-improvement-to-heex-list-comprehensions/64986/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-335237" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="335237"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #12"></div>
  </section>
</div>
    <div class="postbit" id="335283" data-post-id="335283">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="josevalim" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/120/1787_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  josevalim
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Elixir</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="LostKobrakai" data-post="13" data-topic="64986">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/lostkobrakai/48/3072_2.png" class="avatar"> LostKobrakai:</div>
<blockquote>
<p>There is quite a good example in LV: <code>Phoenix.Component.inputs_</code></p>
</blockquote>
</aside>
<p>That’s indeed a very good example of a <em>problem</em>.</p>
<aside class="quote no-group" data-username="LostKobrakai" data-post="13" data-topic="64986">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/lostkobrakai/48/3072_2.png" class="avatar"> LostKobrakai:</div>
<blockquote>
<p>I don’t really understand how this is such a controversial topic</p>
</blockquote>
</aside>
<p>I think there is some confusion here. I don’t think the problem statement you gave is controversial, however, I still think if the proposed solution is possible is controversial/debatable.</p>
<p>To understand precisely what I mean, let’s try to imagine how we could solve this problem. Here is the <code>inputs_for</code> example in latest docs:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">&lt;.form for={@form} phx-change="change_name"&gt;
  &lt;.inputs_for :let={f_nested} field={@form[:nested]}&gt;
    &lt;.input type="text" field={f_nested[:name]} /&gt;
  &lt;/.inputs_for&gt;
&lt;/.form&gt;
</code></pre>
<p>What you want is to, if <code>@form[:nested]</code> represents a collection, you want to automatically compute the diff. The first immediate problem is that the loop today is inside <code>inputs_for</code>. Maybe we could make the <code>key</code> an argument to <code>inputs_for</code> (if it is possible it is a separate discussion) but let’s assume we introduce a new function that allows us to extract the comprehension out:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">&lt;.form for={@form} phx-change="change_name"&gt;
  &lt;%= for f_nested &lt;- inputs_for(@form[:nested]), key: "..." do %&gt;
    &lt;.input type="text" field={f_nested[:name]} /&gt;
  &lt;% end %&gt;
&lt;/.form&gt;
</code></pre>
<p>In order for this to work, we need to figure out what is a good key. But let’s keep it simple for now and say the key is the index. This at least avoids sending all inputs when you only change one. It will likely cause everything to be sent when adding or deleting entries. Good enough for now.</p>
<p>Now that we have a key, we need to figure out how to diff the previous value of <code>inputs_for(@form[:nested])</code> (from the previous render) with the value of <code>inputs_for(@form[:nested])</code> from the new one. The trouble is: template rendering cannot keep state, so we cannot automatically store the comprehension we traversed in the old value. You could say: “we have the previous value of <code>@form</code>, so why don’t we evaluate <code>inputs_for(@form[:nested])</code> again with the old value of <code>@form</code>”?</p>
<p>Well, you could do that for the example so far, but what if it was written as:</p>
<pre><code>for f_nested &lt;- my_inputs_for(@form[:nested], @another_assign), key: ...
</code></pre>
<p>Or this:</p>
<pre><code>for f_nested &lt;- my_inputs_for(@form[:nested], some_function()), key: ...
</code></pre>
<p>We are now saying that rendering a “keyed-comprehension” needs to reevaluate some expression (the right side of <code>&lt;-</code>) in the “past” with 100% fidelity, so we can compare old and new. That looks very hard to do reliably in practice.</p>
<p>That’s the crux of my argument: I don’t think you can simply key a random comprehension without adding a bunch of restrictions.</p>
<p>A simple solution would be to say: “look, you can only key comprehensions if you read the value directly from an assign, this way we can easily track before and after”. So we have something like this:</p>
<pre><code>for f_nested &lt;- @inputs_for_nested do
  ...
end
</code></pre>
<p>And you could set the assign using a special function in LiveView, like this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">socket
|&gt; assign(:form, form)
|&gt; keyed_enum(:inputs_for_nested, inputs_for(form[:nested]), key: ...)
</code></pre>
<p>And when you look at it, that’s precisely what streams do!</p>
<p>In fact, assuming <code>inputs_for</code> exists, I could implement <code>keyed_enum</code> on top of streams <em>today</em>. I just need to compare the previous assign with the new one, based on the <code>key</code>, and generate specific insert/update/delete commands into the stream. All we need to do is to have a function called <code>inputs_for</code> that returns a collection of children forms.</p>
<p>So yes, the solution is a controversial topic because there are several trade-offs that need to be considered. And, for the problem statement you gave, the solution lies (IMO) on adding a function completely unrelated to streams or comprehensions.</p>
<p>Looking at react, solid, vue, and etc is useful but ultimately they run on the client and in a vastly different paradigm. I am afraid we cannot ultimately change where the detection of a record happens, although we could make the generation of a stream from two assigns (old and new) easier.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="335283" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/possible-payload-size-improvement-to-heex-list-comprehensions/64986/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-335283" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="335283"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #13"></div>
  </section>
</div>
    <div class="postbit" id="335326" data-post-id="335326">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="LostKobrakai" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/LostKobrakai/120/3072_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  LostKobrakai
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Those are all fair points – especially in the case of <code>inputs_for</code>.</p>
<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="14" data-topic="64986">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>We are now saying that rendering a “keyed-comprehension” needs to reevaluate some expression (the right side of <code>&lt;-</code>) in the “past” with 100% fidelity, so we can compare old and new. That looks very hard to do reliably in practice.</p>
</blockquote>
</aside>
<p>That a very real issue, but also it’s not one that’s unique to for comprehensions. If you call functions within a template that’s known to break change tracking elsewhere as well. I think we’re on the same boat that this is a problem. Tbh I think that one might just need further additions on the form abstractions, so they could return a plain (deeply nested) datastructure to change track instead of requiring intermediate function calls in the templates.</p>
<p>But a nested (tree) structure means you cannot track list changes only on the top level assigns like streams do, unless you chunk up the tree in nested live_components and have a stream be on each of their top level assigns. I think that would work, but doesn’t feel like a nice experience needing to move to a different module/state context wherever there’s a list comprehension to change track better.</p>
<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="14" data-topic="64986">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>The trouble is: template rendering cannot keep state, so we cannot automatically store the comprehension we traversed in the old value.</p>
</blockquote>
</aside>
<p>This one is the big part here I think. This means we cannot optimize which list in assigns needs tracking from the actual usage within a template. I surely missed to consider that. I’m wondering if a template could relay such information back at some point – at least where there’s no function calls involved. Probably not easily done though.</p>
<p>So I see that for now this constraint would require us to state upfront for all assigns which lists in them should be tracked by which strategy, which to me would already be an improvement. That’s essentially your resolution. I’d love to see if that could work for nested data instead of top level assigns only though.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="335326" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/possible-payload-size-improvement-to-heex-list-comprehensions/64986/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-335326" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="335326"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #14"></div>
  </section>
</div>
    <div class="postbit" id="335347" data-post-id="335347">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Jskalc" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Jskalc/120/35876_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Jskalc
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I agree with LostKobrakai here, and that’s the reason why I’ve started the discussion. Streams are superior in all aspects, but only if you can use them. And you can use them efficiently <strong>only if your context gives you diffs</strong>. Why?</p>
<p>Let’s consider (IMO) the most common case of using list comprehensions - looping through an Ecto collection. For example:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">assign(socket, :users, Accounts.list_users())
</code></pre>
<p>Let’s consider one is not using PubSub for publishing the updates / creates / removals (it can get time-consuming to do if you’d need to do it for all the resources), and would like to periodically refresh visible users. The easiest way is to simply run the assign again, eg. in a periodic <code>handle_info</code>:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def handle_info(:refresh, socket) do
  Process.send_after(self(), :refresh, 10000)
  {:noreply, assign(socket, :users, Accounts.list_users())}
end
</code></pre>
<p>By default, you’ll send the whole collection back to the client, even if nothing was changed <img src="https://forum.elixirforum.com/images/emoji/apple/pensive_face.png?v=15" title=":pensive_face:" class="emoji" alt=":pensive_face:" loading="lazy" width="20" height="20"></p>
<p>Ok, so let’s use streams for that case. Sadly we need to write a diff code by ourselves, otherwise <strong>streams won’t help with the payload at all</strong>. But since we need to do the diff, <strong>we don’t save any memory</strong> because we need old assigns to calculate the diff. Do you agree <a class="mention" href="/u/josevalim" rel="nofollow">@josevalim</a>?</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def handle_info(:refresh, socket) do
  Process.send_after(self(), :refresh, 10000)
  users = Accounts.list_users())
  old_users = socket.assigns.users
  
  added = for u &lt;- users, do: ... # calculate added users
  removed = for u &lt;- users, do: ... # calculate removed users
  updated = for u &lt;- users, do: ... # calculate updated users  

  socket = socket
  |&gt; stream(:users, added ++ updated)
  |&gt; stream_delete(:users, removed)
  
  {:noreply, socket}
end
</code></pre>
<p>So, for a case like this, nobody will consider using streams, because it’s simply too much work for just optimising the payload.</p>
<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="14" data-topic="64986">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>And, for the problem statement you gave, the solution lies (IMO) on adding a function completely unrelated to streams or comprehensions.</p>
</blockquote>
</aside>
<p>Yes, we could do that. So we could write something like</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">stream_update(socket, :users, old_users, new_users)
</code></pre>
<p>and hopefully it would work - we wouldn’t get any benefits regarding memory usage, but at least payload would be optimized.</p>
<p>Just, imagine that users is not a top-level assign, eg an ecto preload:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">assign(socket, :organization, Accounts.get_organization(preload: [:users]))
</code></pre>
<p>If we would like to optimise that, we’d need to introduce top-level stream just for users.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">org = Accounts.get_organization(preload: [:users])
socket
|&gt; assign(:organization, org)
|&gt; stream(:users, org.users)
</code></pre>
<p>Keeping it in-sync with <code>org.users</code> is additional overhead for the developer. But still, we didn’t yet arrive at the “worst” case:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">assign(socket, :users, Accounts.list_users(preload: [:comments]))
</code></pre>
<p>So basically we’d like to render a list comprehension within list comprehension. Optimising it with streams is a lot of work, so the vast majority of developers won’t even try. On the other hand, adding two <code>key</code> attributes should be trivial for the developer.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">&lt;div :for={user &lt;- @users} key={@user.id}&gt;
  &lt;div :for={comment &lt;- user.comments} key={@comment.id}&gt;
     &lt;%= comment.content %&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>And this is also leads me to another point: <strong>Streams are not able to optimise payloads of individual items, because they don’t have the previous value</strong>.  So if we’d consider a stream rendering user like this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">&lt;div :for={{dom_id, user} &lt;- @streams.users} id={dom_id}&gt;
  &lt;img src={user.avatar}/&gt;
  &lt;span&gt;&lt;%= user.name %&gt;&lt;/span&gt;
  &lt;span&gt;&lt;%= user.description %&gt;&lt;/span&gt;
  &lt;span :for={comment &lt;- user.comments} &gt;&lt;%= comment.content %&gt;&lt;/span&gt;
&lt;/div&gt;
</code></pre>
<p>If only <code>name</code> was updated, stream will <strong>still send all of the dynamic values to the client</strong>, since it has no way of determining which values were updated. In my case it’s negligible, but I’ve worked with much more complex collections, consisting of many levels of components. Streams improve the payload by sending less items, but on the other side each item is not optimized.</p>
<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="14" data-topic="64986">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>We are now saying that rendering a “keyed-comprehension” needs to reevaluate some expression (the right side of <code>&lt;-</code>) in the “past” with 100% fidelity, so we can compare old and new. That looks very hard to do reliably in practice.</p>
</blockquote>
</aside>
<p>I think this is the most important problem you see right now, correct? If we could somehow reliably get old keys, both when they’re taken from assigns and from some function calls, then you’d be more keen on supporting that improvement?</p>
<p>Still, doesn’t that issue apply to normal change tracking and is already mostly solved? Eg if someone renders</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">&lt;div :if={check_condition(@current_user)}/&gt;
</code></pre>
<p>then the function is executed each time, and I believe it’s optimised as well (eg. if it evaluated to truthy value both on that render and the previous one coming from <code>__changed__</code>, only the diff will be send?). What happens if the function is non-deterministic and might return different value for the same argument? <img src="https://forum.elixirforum.com/images/emoji/apple/thinking.png?v=15" title=":thinking:" class="emoji" alt=":thinking:" loading="lazy" width="20" height="20"></p>
<hr>
<p>To summarize, I think streams are not the one-fits-all solution, because:</p>
<ul>
<li>you need to have access to diffs, otherwise they optimize only payload size</li>
<li>they’re not able to optimize payloads of individual items</li>
<li>you need a separate assign for each stream (problem if your collection is a field of another assign)</li>
<li>they require a more verbose &amp; careful code (where exactly we want to insert that item? what should be the limit?)</li>
<li>optimizing lists within lists is very hard with streams</li>
</ul>
<p>Implementing this in Phoenix HEEX engine correctly will be very challenging. Still, don’t you think it might be worth at least to try?</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="335347" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/possible-payload-size-improvement-to-heex-list-comprehensions/64986/16">Post #15</a>
	                </div>
	            </div>
              <div id="likers-container-335347" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="335347"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #15"></div>
  </section>
</div>
    <div class="postbit" id="335383" data-post-id="335383">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="josevalim" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/120/1787_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  josevalim
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Elixir</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="Jskalc" data-post="16" data-topic="64986">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jskalc/48/35876_2.png" class="avatar"> Jskalc:</div>
<blockquote>
<p>And this is also leads me to another point: <strong>Streams are not able to optimise payloads of individual items, because they don’t have the previous value</strong>.</p>
</blockquote>
</aside>
<p>I love this argument. <img src="https://forum.elixirforum.com/images/emoji/apple/+1.png?v=15" title=":+1:" class="emoji" alt=":+1:" loading="lazy" width="20" height="20"></p>
<aside class="quote no-group" data-username="Jskalc" data-post="16" data-topic="64986">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jskalc/48/35876_2.png" class="avatar"> Jskalc:</div>
<blockquote>
<p>Still, doesn’t that issue apply to normal change tracking and is already mostly solved? Eg if someone renders</p>
</blockquote>
</aside>
<p>Those are different. Change tracking is used exclusively to say if a part needs to be recomputed or not (it is effectively a boolean) and we can be conservative about it. And even then, what tells you if something changed or not is the assign you do in your <code>handle_*</code> callbacks, not the view. What you want is to compute a previous value (that needs to be reliable!) and detect what changed in the view, and I am really afraid that’s not possible.</p>
<aside class="quote no-group" data-username="Jskalc" data-post="16" data-topic="64986">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jskalc/48/35876_2.png" class="avatar"> Jskalc:</div>
<blockquote>
<p>Implementing this in Phoenix HEEX engine correctly will be very challenging. Still, don’t you think it might be worth at least to try?</p>
</blockquote>
</aside>
<p>Given everything we said, here is what I propose:</p>
<ol>
<li>
<p>Implement a function called <code>stream_diff</code>, that compares two assigns and generates the relevant stream items, as I mentioned in my previous comment. This can be implemented today using the existing APIs and it would be called in your <code>handle_*</code>callbacks</p>
</li>
<li>
<p>Introduce a mechanism in streams that allow you to say “I have the previous element too, so generate the proper change tracking code”, this will require changes to Elixir and the JavaScript client. We could add this to the existing API, such as <code>stream_insert(socket, :foos, new_element, previous: ...)</code>. Then we can change <code>stream_diff</code> to use it</p>
</li>
</ol>
<p>So ultimately we are still using streams but we make “diffing lists” much simpler.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="335383" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/possible-payload-size-improvement-to-heex-list-comprehensions/64986/17">Post #16</a>
	                </div>
	            </div>
              <div id="likers-container-335383" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="335383"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #16"></div>
  </section>
</div>
    <div class="postbit" id="335386" data-post-id="335386">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Schultzer" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Schultzer/120/4339_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Schultzer
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Nice!</p>
<p>I always found that the stream API was missing this and I had to manually use JS to deal with this to improve performance of a big complex realtime database management app.</p>
<p>So improvement here is very welcome!</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="335386" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/possible-payload-size-improvement-to-heex-list-comprehensions/64986/18">Post #17</a>
	                </div>
	            </div>
              <div id="likers-container-335386" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="335386"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #17"></div>
  </section>
</div>
    <div class="postbit" id="335401" data-post-id="335401">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Jskalc" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Jskalc/120/35876_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Jskalc
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I like your idea <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"> This solves everything except the need to use streams as a top-level assign and synchronizing it with eg. preloads.</p>
<p><code>stream_diff</code> seems like already almost implemented in ecto <code>put_assoc</code> (I assume it figures out what to update / delete / insert)?</p>
<p>Maybe after this is done we could think about further improvements <img src="https://forum.elixirforum.com/images/emoji/apple/wink.png?v=15" title=":wink:" class="emoji" alt=":wink:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="335401" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/possible-payload-size-improvement-to-heex-list-comprehensions/64986/19">Post #18</a>
	                </div>
	            </div>
              <div id="likers-container-335401" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="335401"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #18"></div>
  </section>
</div>
    <div class="postbit" id="335629" data-post-id="335629">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Jskalc" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Jskalc/120/35876_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Jskalc
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I’m thinking about the implementation and there are some non-obvious challenges.</p>
<hr>
<ol>
<li>Let’s start with the parameters of <code>stream_diff</code>.</li>
</ol>
<pre data-code-wrap="elixir"><code class="lang-elixir">def stream_diff(socket, stream_name, current_items, previous_items, opts \\ [])
</code></pre>
<p>would it be enough? (opts for some options that might be needed in the future, if we won’t find anything relevant we could remove it)</p>
<hr>
<ol start="2">
<li>The bigger issue is with <strong>reordering</strong>. So let’s imagine we have a stream with two items. We swapped their order, and called <code>stream_diff</code>. Currently it would need to be solved by <strong>removing the first value and inserting the second one,</strong> correct? Reversing a big list would be even worse.</li>
</ol>
<p>Should we consider such a case and try to optimise it as well? We could introduce sth like</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def stream_reorder(socket, stream_name, changes)
#. ...

stream_reorder(socket, :test, %{1 =&gt; 3, 2 =&gt; 5})
</code></pre>
<p>Without this it’s impossible to make it really optimised. Case of reordering seems important for eg. sortable tables.</p>
<hr>
<p>Than you in advance! Btw, what would you think would be a better place to have such a discussion about implementation details? Here as well or maybe open an empty PR just for a discussion?</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="335629" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/possible-payload-size-improvement-to-heex-list-comprehensions/64986/20">Post #19</a>
	                </div>
	            </div>
              <div id="likers-container-335629" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="335629"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #19"></div>
  </section>
</div>
    <div class="postbit" id="335634" data-post-id="335634">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="josevalim" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/120/1787_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  josevalim
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Elixir</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I would not tackle reordering now. I think if you have all of these requirements and reordering, maybe it is fine to tell you to use LiveComponents, which solves the problems mentioned here as well.</p>
<p>Something we also discussed is to have a a phx-order attribute for containers inside streams and the browser will sort according to it. Either way, I’d say it is a separate problem.</p>
<p>I think discussing here is fine. I am not yet sure if something like stream_diff will be added to LV but some of the underlying improvements (such as :previous for stream_insert and phx-order) should.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="335634" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/possible-payload-size-improvement-to-heex-list-comprehensions/64986/21">Post #20</a>
	                </div>
	            </div>
              <div id="likers-container-335634" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="335634"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #20"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <a class="load-more-button" data-turbo-stream="true" href="/topics/64986/load_more?page=3">Load more posts (30 remaining)</a>
</div></template></turbo-stream>