tmbb

tmbb

@josevalim has published this video, where he livecodes some improvemens to EEx templates based on ideas from PhoenixUndeadView/Vampyre: Twitch

It was very interesting to see @josevalim going more or less down the same paths while solving a problem similar to my own with my Vampyre/PhoenixUndeadView project.

It’s amazing the way he managed to live code all of that without any preparation. My own solution to more or less the same problem is clearly over-engineered when compared to @josevalim’s solution, but it solves a different problem with different requirements, so it can’t be directly compared. My implementation also took much longer and is not as elegant (again, different requirements, so they can’t be directly compared).

I guess the main take-away from @josevalim’s video is that one can be very naïve when naming the variables, because variables nested deeper into the template will not overwrite the ones outside. I believe I had proved that to myself, but I still wanted unique names, so I went ahead anyway… I still think there is some value in having unique variable names (it makes it easier to inspect the compiled output), but seeing how much simpler it is to implement it the way @josevalim did it, I guess I think it’s not worth it to do it like I did.

The main difference between the new EEx improvements and Vampyre (and the reason why you can’t compare both probjects) is that EEx doesn’t attempt to expand macros and optimize the result (it wouldn’t even make sense in the case of such a generic project as EEx). It might make sense in a more specialized engine, like Vampyre, or the engine in Phoenix.HTML, where one expects a library of widgets to be available. That way, optimizing those widgets as much as possible makes sense.

From now on, I and @josevalim will probably pursue further optimizations in different directions, as explained on this github issue.

I’m still betting on using macros to do as much work as possible at compile-time and generate templates which are as optimized as possible, and regenerate all the dynamic parts each time the template is rendered. That is not as bad as it sounds, as I’ve managed to make the dynamic parts as minimal as possible and to make my templates as flat as possible.

On the other hand, @josevalim is now thinking about optimizing the templates by building a dependency graph on the template assigns, so that it’s possible to optimize only those segment that depend on the data that has changed.

Anyway, this was an amazing video

Showing Posts 11 to 20

tmbb

tmbb OP

Yeah, sending ,"... segment ..." is slightly less data than index:"... segment ...". That’s what I meant. And for a flat array I can have even more effucient encodings.

tmbb

tmbb OP

This is amazing. I can’t believe I’ve missed this optimization! This is probably bigger than my inline stuff. It requires a slightly smarter Javascript client, of course, but it’s a great idea.

And one I can implement in Vampyre, too.

tmbb

tmbb OP

Are you sure you eant to apply this optimization on regular rendering? This makes the initial render dependent on Javascript, which I’m not sure is a good idea.

josevalim

josevalim

Creator of Elixir

I wanted to say the first rendering for the JS client. Good catch.

tmbb

tmbb OP

@josevalim, do you already have a strategy to optimize form_for/4 without turning it into a macro? I’m extremely curious about what you’ll come up with

josevalim

josevalim

Creator of Elixir

We will go with the simplest approach for now which is to convert this:

<%= form_for @changeset, ..., fn -> %>
  <%= text_input f, :name %>
<% end %>

into this:

<%= f = form_for @changeset, ... %>
  <%= text_input f, :name %>
</form>

Effectively removing the nesting. Not the prettiest thing but definitely the simplest that works.

tmbb

tmbb OP

Hm… I don’t see how this can work. How is it possible that f is simultaneously a %Phoenix.HTML.Form{} struct and a binary containing the opening <form> tag, the CSRF token and the other hidden input tags that forms in Phoenix have?

tmbb

tmbb OP

You’d have to define something like:

<% f = Phoenix.HTML.FormData.to_form(@changeset) %>
<%= form_tag f %>
  <%= text_input f, :name %>
</form>

which I actually prefer instead of your version above.

On further thought, I wonder if optimizing forms is such a big deal. With the lack on inlining, it’s possible that you don’t gain much if you’re not inlining segments (which Vampyre can do but LiveEEx can’t - at least not yet). After all, forms in phoenix depend a lot on HTML generating functions. For example, let’s look at an example of a form generated by a Phoenix generator:

<%= form_for @changeset, @action, fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below.</p>
    </div>
  <% end %>

  <%= label f, :field1 %>
  <%= text_input f, :field1 %>
  <%= error_tag f, :field1 %>

  <%= label f, :field2 %>
  <%= text_input f, :field2 %>
  <%= error_tag f, :field2 %>

  <%= label f, :field3 %>
  <%= text_input f, :field3 %>
  <%= error_tag f, :field3 %>

  <div>
    <%= submit "Save" %>
  </div>
<% end %>

The only static segments inside the form are sequences of whitespaces, <div> and </div> at the end. Also, almost all of the dynamic segments depend indirectly on the changeset. And on f, of course. LiveEEx doesn’t have a way of determining which fields to update when f (or @changeset) changes, so it will have to regenerate all widgets which depend on f, which are all of them.

You will also have to regenerate the CSRF token, unless you mark that as static somehow, as well as the other hidden input tags.

If you’re going to have to generate almost the whole form, maybe it’s not worth it to optimize that part.

On the other hand, in Vampyre I can optimize most almost all input fields to mostly static data with just 3 dynamic segments, with most of it being static.

<input name="<%= form.name %>[field1]" id="<%= form.name %>_field1" value="<%= form_value(form, field) %>">

And the form.name segments can be optimized into static segments if we assume that the form shape won’t change (which is pretty much true for almost all forms).

PS: I’ve just noticed on reviewing my code that my form_for macro doesn’t support setting the value attribute yet, but supporting it is a very minor change.

josevalim

josevalim

Creator of Elixir

We just need to implement Phoenix.HTML.Safe for it.

It is most likely that actual user forms are more complex than this. It also serves as a good example on how to handle nesting and it opens up the possibility to explore other approaches later.

I think if there is a lesson in this whole discussion is that starting simple allows us to incrementally improve based on new ideas, feedback, etc. If I had started thinking about how to optimize comprehensions, we may have ended-up with a more complex solution and very different than the one today. I am happy to go with baby steps.

tmbb

tmbb OP

Well, it goes both ways xD If I had started thinking about how to optimize for comprehensions (instead of quitting), I’d have stumbled upon your solution about 1 month ago :stuck_out_tongue: It’s a natural consequence of trying to do things at compile time, because we can take advantage of the fact that for is a special form that can’t be overriden, so it can be analyzed statically.

But I believe we may have started with different priorities.

You had @chrismccord’s example from the LiveView talk and and maybe optimizing such dynamic highly dynamic templates was a priority for you.

I started thinking about (mostly static) forms and how to optimize those, because those are the ones I saw myself using the most, especially the part of having real-time form validation without writing any JS. That brought me naturally close to the idea of inlining static parts and merging adjacent binaries together. Yes, live tables that could update in response to typing in a search field were cool, but I had already given up on those :stuck_out_tongue:

I’m the first to praise the simplicity of your implementation but I think most of the complexity with my approach is unavoidable. It’s the result of implementing an optimizing compiler (although a very simple one) that tries to separate the static and dynamic parts in a very obsessive way.

The other main source of complexity with my approach is probably the attempt to maintain backward compatibility with Phoenix.HTML, which contains some things that are not very easy to optimize statically. The tag() function, in particular, is a bit weird, and I should probably break compatibility so that it can be abetter building block for the rest. If I can encapsulate most of the complexity inside the tag() macro (because I need it to be a macro), thins will probably be much simpler in the rest of the code base.

I don’t consider this “premature” optimization, because it’s essential for my main goal, which is to send the absolute minimum amount of data over the network for things like forms.

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 94592 917
New
cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
AstonJ
Since we have deprecated our Erlang sections (as we have dedicated Erlang Forums now) let’s add this thread for those who’d like to post ...
New
maennchen
:warning: Security advisory: Decimal DoS vulnerability A vulnerability has been published for decimal where very large exponents can cau...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews