<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matt Whitworth</title>
    <description>The latest articles on DEV Community by Matt Whitworth (@mwhitworth).</description>
    <link>https://dev.to/mwhitworth</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1619533%2Ff79ec07c-7171-4e87-83bf-94e5484e5448.png</url>
      <title>DEV Community: Matt Whitworth</title>
      <link>https://dev.to/mwhitworth</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mwhitworth"/>
    <language>en</language>
    <item>
      <title>Tooltips in Phoenix LiveView</title>
      <dc:creator>Matt Whitworth</dc:creator>
      <pubDate>Thu, 13 Feb 2025 19:45:06 +0000</pubDate>
      <link>https://dev.to/puretype/tooltips-in-phoenix-liveview-k8e</link>
      <guid>https://dev.to/puretype/tooltips-in-phoenix-liveview-k8e</guid>
      <description>&lt;p&gt;There are a few options to integrate tooltip functionality into Phoenix LiveView. This article covers integrating a Phoenix LiveView with one popular library tippy.js, ensuring any updates from LiveView are reflected in tooltip state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;tippy.js is a powerful, versatile and lightweight Javascript tooltip library. It provides the logic (and optional styling) of elements that “pop out” and float next to a target element. Although the last version was released three years ago, it receives over a million downloads a week. The approach here can be transferred to other libraries like Floating UI.&lt;/p&gt;

&lt;p&gt;With Phoenix LiveView applications, we may wish to have the content of tooltips (and their styling) vary depending on the current application state.&lt;/p&gt;

&lt;p&gt;Because tippy.js takes care of configuring and rendering the tooltip, and stores internal state outside of the element, we’ll need to use a LiveView client hook to ensure updates to the element are reflected in the tippy.js internal state associated with the element.&lt;/p&gt;

&lt;p&gt;(Need a refresher on how to integrate client-side libraries that take care of rendering? See this previous article: Phoenix LiveView, hooks, and push-event: json-view)&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;In a Phoenix LiveView application, let's install the library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--prefix&lt;/span&gt; assets tippy.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll need to now include the Javascript and (default) CSS in separate ways.&lt;/p&gt;

&lt;p&gt;For the Javascript code - We can then include tippy.js in assets/js/app.js, with the hook to come:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tippy&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tippy.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the CSS can be imported in assets/css/app.css:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"tippy.js/dist/tippy.css"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring tooltips (tippy.js)
&lt;/h2&gt;

&lt;p&gt;With this library now included in our application, we can now invoke &lt;code&gt;tippy&lt;/code&gt;, a function that creates tooltip context associated with a particular element. We can use the returned value (an &lt;code&gt;instance&lt;/code&gt;) to control, update and invoke the tooltip as necessary.&lt;/p&gt;

&lt;p&gt;tippy.js includes a number of configurable properties (with associated defaults) - &lt;a href="https://atomiks.github.io/tippyjs/v6/all-props/" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt; covers all of these.&lt;/p&gt;

&lt;p&gt;We can keep the need for custom Javascript code very light, and integrate with LiveView well, by using &lt;code&gt;data-tippy&lt;/code&gt; attributes. We can access these properties via the &lt;code&gt;dataset&lt;/code&gt; attribute, which we’ll do so in the hook in the following section.&lt;/p&gt;

&lt;p&gt;When creating the tooltip instance, the &lt;code&gt;tippy&lt;/code&gt; function actually reads these &lt;code&gt;data-tippy&lt;/code&gt; elements. However, if we update those attribute values with LiveView, it won’t update the tooltip. This is a limitation of the library, &lt;a href="https://atomiks.github.io/tippyjs/v6/faq/#changing-data-tippy-attributes-does-not-update-the-tooltip" rel="noopener noreferrer"&gt;documented in the FAQ&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, keeping LiveView’s update mechanism in mind, we’ll have to write a hook to create the tooltip instance when the element is mounted, and update the properties correctly when the element is updated.&lt;/p&gt;

&lt;p&gt;Let’s take a look at the element first, to see what kind of state we’ll need to deal with.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HTML element
&lt;/h2&gt;

&lt;p&gt;Here’s a &lt;code&gt;button&lt;/code&gt; element that will integrate with the hook we’re about to write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"example-button"&lt;/span&gt; &lt;span class="na"&gt;phx-hook=&lt;/span&gt;&lt;span class="s"&gt;"Tippy"&lt;/span&gt; &lt;span class="na"&gt;data-tippy-content=&lt;/span&gt;&lt;span class="s"&gt;"Hello!"&lt;/span&gt; &lt;span class="na"&gt;data-tippy-placement=&lt;/span&gt;&lt;span class="s"&gt;"bottom"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Hover over me
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are several things to note here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; is required for all elements that have a &lt;code&gt;phx-hook&lt;/code&gt; associated with them, so Phoenix LiveView can associate internal state by element ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;phx-hook&lt;/code&gt; associates a particular hook. Only (at most) one hook is permitted per element&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data-tippy-&lt;/code&gt; is the prefix for each attribute. We’ll need to process these &lt;code&gt;dataset&lt;/code&gt; elements (see &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset" rel="noopener noreferrer"&gt;the dataset property&lt;/a&gt; documentation) to pass a configuration object when we come to update the properties associated with the tooltip. In short: &lt;code&gt;data-tippy-placement=”bottom”&lt;/code&gt; in the DOM becomes &lt;code&gt;this.el.dataset.tippyPlacement // =&amp;gt; “bottom”&lt;/code&gt; in Javascript.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The LiveView hook
&lt;/h2&gt;

&lt;p&gt;Here’s the code for the hook. Let’s go through the below section by section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hooks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Tippy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tippy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tippy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tippyPropName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nx"&gt;v&lt;/span&gt;
            &lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;destroyed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="nf"&gt;tippyPropName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;strippedName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tippy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;strippedName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;strippedName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  mounted()
&lt;/h3&gt;

&lt;p&gt;When the element is first added to the page, the mounted() callback is invoked. We’ll need to create the tooltip instance associated with the DOM element (this.el):&lt;/p&gt;

&lt;p&gt;this.instance = tippy(this.el);&lt;/p&gt;

&lt;p&gt;After this, tippy.js takes care of attaching to the relevant DOM events and invoking the logic to display the tooltip.&lt;/p&gt;

&lt;h3&gt;
  
  
  updated()
&lt;/h3&gt;

&lt;p&gt;As noted above, tippy.js doesn’t update its internal state if the values of attributes change.&lt;/p&gt;

&lt;p&gt;this.instance.setProps(&lt;br&gt;
    Object.fromEntries(&lt;br&gt;
      Object.entries(this.el.dataset)&lt;br&gt;
        .filter(([k]) =&amp;gt; k.startsWith("tippy"))&lt;br&gt;
        .map(([k, v]) =&amp;gt; [&lt;br&gt;
          this.tippyPropName(k),&lt;br&gt;
          v&lt;br&gt;
        ])&lt;br&gt;
    )&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;Step by step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It starts with &lt;code&gt;this.el.dataset&lt;/code&gt;, which contains all data attributes of an HTML element.&lt;/li&gt;
&lt;li&gt;It filters these attributes to only include those that start with "tippy" (&lt;code&gt;data-tippy&lt;/code&gt; in the HTML)&lt;/li&gt;
&lt;li&gt;For each filtered attribute, it transforms the attribute name into a valid Tippy.js property name using a tippyPropName method.&lt;/li&gt;
&lt;li&gt;It creates an object from these transformed key-value pairs.&lt;/li&gt;
&lt;li&gt;Finally, it sets these properties on the Tippy.js instance using setProps().&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(Take care that the representation within the tooltip’s data attributes is something that is either a string or a JSON representation, depending on the type of the tippy.js attribute.)&lt;/p&gt;

&lt;p&gt;Another alternative: we could invoke &lt;code&gt;tippy&lt;/code&gt; again to create another tooltip. But this has the clearest purpose and intention, even if the logic to transform attribute names isn’t necessarily easy to parse.&lt;/p&gt;

&lt;h3&gt;
  
  
  destroyed()
&lt;/h3&gt;

&lt;p&gt;Without this, tippy.js tooltip instances accumulate during LiveView updates, causing memory leaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  tippyPropName(k)
&lt;/h3&gt;

&lt;p&gt;We need to remove the &lt;code&gt;tippy&lt;/code&gt; prefix from the property name, and convert the casing of the remaining string (from a leading uppercase character to a lower one). So &lt;code&gt;tippyShowOnCreate&lt;/code&gt; becomes &lt;code&gt;showOnCreate&lt;/code&gt;, the object key that the &lt;code&gt;setProps&lt;/code&gt; method expects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tippy.js&lt;/code&gt; is by no means the only tooltip library that can be integrated with Phoenix LiveView:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ryangjchandler/alpine-tooltip" rel="noopener noreferrer"&gt;alpine-tooltip&lt;/a&gt; is a popular plugin for Alpine.js, commonly used as a simple Javascript dynamic layer in Phoenix LiveView applications&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://floating-ui.com/docs/getting-started" rel="noopener noreferrer"&gt;Floating UI&lt;/a&gt; is a library (superseding popper.js) that helps you create “floating” elements such as tooltips, popovers, dropdowns and more. Floating UI is framework-independent.&lt;/li&gt;
&lt;li&gt;Use Phoenix LiveView component libraries like &lt;a href="https://github.com/bluzky/salad_ui" rel="noopener noreferrer"&gt;salad_ui&lt;/a&gt; or &lt;a href="https://mishka.tools/chelekom" rel="noopener noreferrer"&gt;Chelekom&lt;/a&gt;, which contain their own tooltip component implementations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Integrating tippy.js with Phoenix LiveView provides a powerful and flexible solution for implementing tooltips in your application. By utilizing a LiveView client hook, we can ensure that any updates to the tooltip content or styling are seamlessly reflected in the tippy.js internal state.&lt;/p&gt;

&lt;p&gt;This approach allows for dynamic, state-dependent tooltips that respond to changes in the application's context, enhancing the user experience and interactivity of your Phoenix LiveView application.&lt;/p&gt;

&lt;p&gt;While tippy.js is a popular choice, it's worth noting that there are alternative libraries and approaches available for implementing tooltips in Phoenix LiveView. Whether you choose tippy.js, Alpine.js plugins, Floating UI, or Phoenix-specific component libraries, the key is to find a solution that best fits your project's needs and integrates smoothly with LiveView's update mechanism.&lt;/p&gt;

</description>
      <category>liveview</category>
      <category>elixir</category>
      <category>tooltips</category>
    </item>
    <item>
      <title>Phoenix LiveView, hooks and push_event: json_view</title>
      <dc:creator>Matt Whitworth</dc:creator>
      <pubDate>Tue, 10 Dec 2024 16:50:50 +0000</pubDate>
      <link>https://dev.to/mwhitworth/phoenix-liveview-hooks-and-pushevent-jsonview-3pj7</link>
      <guid>https://dev.to/mwhitworth/phoenix-liveview-hooks-and-pushevent-jsonview-3pj7</guid>
      <description>&lt;p&gt;&lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html" rel="noopener noreferrer"&gt;Phoenix LiveView&lt;/a&gt; is a powerful framework built on Elixir, and offers an innovative approach to building real-time web interfaces without the need for extensive Javascript.&lt;/p&gt;

&lt;p&gt;However, integrating with existing client-side libraries, like those for graphing, visualisation or rendering, can sometimes prove a challenge.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore how we can integrate Phoenix LiveView with Javascript libraries that render to the DOM directly.&lt;/p&gt;

&lt;p&gt;We’ll come across &lt;strong&gt;hooks&lt;/strong&gt;, which allow us to run Javascript on certain lifecycle elements for a given element, and how these hooks enable streams of &lt;strong&gt;events&lt;/strong&gt; (using &lt;strong&gt;&lt;code&gt;push_event&lt;/code&gt;&lt;/strong&gt; in LiveView and &lt;strong&gt;&lt;code&gt;pushEvent&lt;/code&gt;&lt;/strong&gt; in Javascript) to allow bi-directional real-time communication between client and server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;TLDR: In Phoenix LiveView, you can use &lt;a href="https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook" rel="noopener noreferrer"&gt;hooks&lt;/a&gt; and &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#push_event/3" rel="noopener noreferrer"&gt;push_event&lt;/a&gt; to push data, and have the client-side library handle the rendering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pgrabovets/json-view" rel="noopener noreferrer"&gt;json-view&lt;/a&gt; (&lt;a href="https://pgrabovets.github.io/json-view/" rel="noopener noreferrer"&gt;demo&lt;/a&gt;) - which displays JSON objects on a webpage as a tree that can be expanded and collapsed - is a good example of how some client-side Javascript libraries take control of rendering to the DOM and interaction with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo441zotg9jvpjsqk95s8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo441zotg9jvpjsqk95s8.jpg" alt="An example of json-view’s interactive output from the demo" width="486" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s work through an example of how to integrate this with JSON data provided by the (LiveView) server. We’ll send some static data from the backend in response to an event, but this data could come from any source.&lt;/p&gt;

&lt;p&gt;The following will assume you’ve set up a Phoenix project using &lt;a href="https://hexdocs.pm/phoenix/Mix.Tasks.Phx.New.html" rel="noopener noreferrer"&gt;mix phx.new&lt;/a&gt;, with LiveView enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Library setup
&lt;/h2&gt;

&lt;p&gt;There are multiple ways to incorporate a Javascript package as part of the page’s Javascript.&lt;/p&gt;

&lt;p&gt;Recommending a preferred setup is outside the scope of this article, but there are two main ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;assets/vendor&lt;/code&gt;&lt;/strong&gt; - in the typical LiveView project template, &lt;code&gt;topbar&lt;/code&gt; is placed in this directory and included in the &lt;code&gt;assets/app/app.js&lt;/code&gt; entrypoint file. This is suitable for smaller, more stable libraries in general, particularly if a single file version of the library is provided.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NPM/Yarn&lt;/strong&gt; - &lt;code&gt;esbuild&lt;/code&gt; and &lt;code&gt;webpack&lt;/code&gt; can pack referenced &lt;code&gt;node_modules&lt;/code&gt; file into a single distribution file. This is suitable for larger, more complex libraries that change more frequently.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The problem with json-view
&lt;/h2&gt;

&lt;p&gt;There are two incompatible models of rendering when we try to combine the two libraries.&lt;/p&gt;

&lt;p&gt;LiveView follows the &lt;strong&gt;declarative model&lt;/strong&gt; - you design the view, determine what data should be displayed, and LiveView works out which elements of the page need to change when the underlying data is changed.&lt;/p&gt;

&lt;p&gt;It achieves this by using socket assigns in &lt;code&gt;render&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
  &amp;lt;p&amp;gt;Hello &amp;lt;%= @name %&amp;gt;&amp;lt;/p&amp;gt;
  """&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, libraries like &lt;code&gt;json-view&lt;/code&gt; work on the &lt;strong&gt;imperative model&lt;/strong&gt;. We specify the steps required to display the data in Javascript, outside the layout of the DOM itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jsonview&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@pgrabovets/json-view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"name": "json-view", "version": "1.0.0"}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;jsonview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.tree&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two models are at odds. We have seemingly no way to match up the two methods of rendering data (declarative and imperative), but we need libraries like &lt;code&gt;json-view&lt;/code&gt; to render rich interfaces on the client.&lt;/p&gt;

&lt;p&gt;Fundamentally - we need to run Javascript code when the server prompts a change of page state. Let’s take a look at &lt;strong&gt;hooks&lt;/strong&gt;, which helps to reconcile these two rendering models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hook setup
&lt;/h2&gt;

&lt;p&gt;In LiveView, &lt;strong&gt;hooks&lt;/strong&gt; are the way to provide bidirectional communication between the (browser) client and (LiveView) server, with the core object of communication being the &lt;strong&gt;event&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Hooks are defined in the &lt;code&gt;LiveSocket&lt;/code&gt; and attached to an element using the &lt;code&gt;phx-hook&lt;/code&gt; attribute. There are a number of callbacks - but we’ll focus on the &lt;code&gt;mounted&lt;/code&gt; callback, since we can co-ordinate everything else via events. You can supply callbacks for other life-cycle events too - &lt;a href="https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook" rel="noopener noreferrer"&gt;check the docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Within a callback like &lt;code&gt;mounted&lt;/code&gt;, the hook sends events to the backend using &lt;code&gt;this.pushEvent&lt;/code&gt;, and handles events from the server by registering a handler for a particular event name using &lt;code&gt;this.handleEvent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It’s important to note only one hook is permitted per element, so you only need to reason about one stream of events between client and server.&lt;/p&gt;

&lt;p&gt;With that knowledge in mind  - let’s define a &lt;code&gt;JsonView&lt;/code&gt; hook that registers an event handler, that eventually calls &lt;code&gt;jsonview.render&lt;/code&gt; on the element to render the tree of data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Socket&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;phoenix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jsonview&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@pgrabovets/json-view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;JsonViewHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;render_json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;jsonview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;liveSocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LiveSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/live&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;JsonView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JsonViewHook&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;...}&lt;/span&gt;
 &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We do several things in these several lines of code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We define a &lt;strong&gt;&lt;code&gt;JsonViewHook&lt;/code&gt;&lt;/strong&gt; object with a &lt;strong&gt;&lt;code&gt;mounted&lt;/code&gt;&lt;/strong&gt; function. This function will be called when the element with this hook is mounted in the DOM.&lt;/li&gt;
&lt;li&gt;Inside &lt;strong&gt;&lt;code&gt;mounted&lt;/code&gt;&lt;/strong&gt;, we set up an event listener for a custom event called "render_json". This event will be triggered from our LiveView.&lt;/li&gt;
&lt;li&gt;When the event is received, it expects a &lt;strong&gt;&lt;code&gt;data&lt;/code&gt;&lt;/strong&gt; parameter containing the JSON to be rendered.&lt;/li&gt;
&lt;li&gt;We clear the existing content of the element.&lt;/li&gt;
&lt;li&gt;We use &lt;strong&gt;&lt;code&gt;jsonview.create&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;jsonview.render&lt;/code&gt;&lt;/strong&gt; to render the JSON data into our element.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To use this hook - we’ll just need to add the &lt;code&gt;phx-hook&lt;/code&gt; attribute with the name of the hook (&lt;code&gt;”JsonView”&lt;/code&gt;) to an element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"json-container"&lt;/span&gt; &lt;span class="na"&gt;phx-hook=&lt;/span&gt;&lt;span class="s"&gt;"JsonView"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sending events to the server
&lt;/h2&gt;

&lt;p&gt;We’ll just need to trigger an event from the backend to provide this data. We’ll leave this outside the scope of this article for now - perhaps a button could trigger an event to the backend? - but you could use &lt;code&gt;this.pushEvent&lt;/code&gt; from the &lt;code&gt;mounted&lt;/code&gt; hook like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pushEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send_json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to send an event to the LiveView server that can be handled with &lt;code&gt;handle_info&lt;/code&gt;. &lt;a href="https://hexdocs.pm/phoenix_live_view/js-interop.html#client-server-communication" rel="noopener noreferrer"&gt;The relevant section of the LiveView documentation&lt;/a&gt; covers more possibilities with &lt;code&gt;this.pushEvent&lt;/code&gt;, including a scenario where a handler function can be specified to handle a reply payload directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending events from the server
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#push_event/3" rel="noopener noreferrer"&gt;push_event/3&lt;/a&gt; is the way to push an event from LiveView to the browser. It can be called at any point - including in &lt;code&gt;mount&lt;/code&gt; - though a good practice is to ensure that the page and all its elements are in a known state before sending these events. Otherwise - you’ll have events silently being dropped during page setup - a sure route to unpredictability!&lt;/p&gt;

&lt;p&gt;Let’s write a &lt;code&gt;handle_event&lt;/code&gt; clause for an event we might receive from the client, which pushes an event to the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"send_json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;push_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"render_json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]})}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! And there’s flexibility here too. Registering event handlers with names on both the client and the server marks a clear separation between the handling of the event and how the event is raised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further integration
&lt;/h2&gt;

&lt;p&gt;We’ve seen a simple example of the integration between client and server. A common extension of this would be to scope certain updates to certain elements, through the use of event parameters and element attributes. This helps to reduce the amount of unnecessary events and handler work.&lt;/p&gt;

&lt;p&gt;This event handling can also be expanded for tighter client-side library integration with the backend. For example, typically these Javascript libraries emit higher-level user interaction events. Our library example, &lt;code&gt;json-view&lt;/code&gt;, doesn’t do this, but a charting library like &lt;a href="https://www.chartjs.org/" rel="noopener noreferrer"&gt;chart.js&lt;/a&gt; does.&lt;/p&gt;

&lt;p&gt;However, from a user interaction standpoint, a round-trip to the server for processing should be generally avoided. Typically, any rendering update based on events would be handled by the rendering library client-side.&lt;/p&gt;

&lt;p&gt;But capturing user activity for other reasons is a common use case. This includes monitoring, logging and analysis. These don’t require a response, so &lt;code&gt;pushEvent&lt;/code&gt; from within a hook can be ideal for this type of asynchronous processing on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Integrating powerful Javascript client-side libraries that require control over the DOM is a key part of creating rich, dynamic user interfaces. Not all page updates need to be real-time, and so retaining LiveView offers a powerful yet simple way to continue to control other page state.&lt;/p&gt;

&lt;p&gt;Becoming familiar with LiveView hooks and their associated events makes integrating these with data that originates from the server possible. It’s also important to note that not all user interactivity require a round-trip to the server, and the interfaces you build will be more flexible and responsive when you use powerful Javascript libraries.&lt;/p&gt;

</description>
      <category>liveview</category>
      <category>elixir</category>
      <category>javascript</category>
    </item>
    <item>
      <title>For loops and comprehensions in Elixir - transforming imperative code</title>
      <dc:creator>Matt Whitworth</dc:creator>
      <pubDate>Tue, 03 Dec 2024 17:18:25 +0000</pubDate>
      <link>https://dev.to/mwhitworth/for-loops-and-comprehensions-in-elixir-transforming-imperative-code-nn3</link>
      <guid>https://dev.to/mwhitworth/for-loops-and-comprehensions-in-elixir-transforming-imperative-code-nn3</guid>
      <description>&lt;p&gt;In this article, we’ll cover some common uses of for loops and comprehensions in Python, how to analyze an existing loop, and how to transform them into their equivalent expressions in Elixir, using the functions in the &lt;strong&gt;Enum&lt;/strong&gt; module and &lt;strong&gt;comprehensions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We’ll focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transforming a collection of data through a function (&lt;strong&gt;map&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;filtering values into or out of a collection (&lt;strong&gt;filter&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;producing a single aggregate value or structure, such as an average (&lt;strong&gt;reduce&lt;/strong&gt; or &lt;strong&gt;fold&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll finish off with a basic example that combines all three!&lt;/p&gt;

&lt;h2&gt;
  
  
  Python
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For loops
&lt;/h3&gt;

&lt;p&gt;In Python, &lt;strong&gt;for loops&lt;/strong&gt; typically feature interleaved processing - the steps are combined together into the same clause or body. Here’s an example that squares the first two even numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: 20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One challenge of this interleaved body is to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;identify each step, and…&lt;/li&gt;
&lt;li&gt;work out what type of step it is.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Breaking apart each step allows you to understand the transformations taking place, eliminate any unnecessary ones, and rewrite those steps into another language construct or higher-level function.&lt;/p&gt;

&lt;p&gt;Annotating the function above results in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;## Filter
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;## Reduce (result += ) and Map (num ** 2)
&lt;/span&gt;        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: 20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The steps
&lt;/h3&gt;

&lt;p&gt;As a result - the order of steps are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Filter&lt;/strong&gt; “out” odd numbers/”in” even numbers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map&lt;/strong&gt; numbers (e.g. 2) to their corresponding square number (e.g. 4)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce&lt;/strong&gt; to a sum of the squared even numbers&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Comprehensions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Comprehensions&lt;/strong&gt; in Python are simple ways to &lt;strong&gt;map&lt;/strong&gt; and &lt;strong&gt;filter&lt;/strong&gt; collections like lists and dictionaries. They don’t offer a way to &lt;strong&gt;reduce&lt;/strong&gt; the result, but we can use built-in functions like &lt;code&gt;sum&lt;/code&gt; to transform the above to process the result of the comprehension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: 20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With comprehensions, the expression divides the &lt;strong&gt;&lt;em&gt;map&lt;/em&gt;&lt;/strong&gt; step (&lt;code&gt;num ** 2&lt;/code&gt;) and &lt;strong&gt;&lt;em&gt;filter&lt;/em&gt;&lt;/strong&gt; step (&lt;code&gt;if num % 2 == 0&lt;/code&gt;) clearly. &lt;code&gt;sum&lt;/code&gt; is the &lt;strong&gt;&lt;em&gt;reduce&lt;/em&gt;&lt;/strong&gt; step here.&lt;/p&gt;

&lt;p&gt;It’s easy to skim through these comprehension expressions in Python, and it places a useful upper limit on the complexity of a comprehension.&lt;/p&gt;

&lt;p&gt;With this background, and a better understanding of the structure and limitations of Python’s processing constructs, let’s proceed to rewriting the above Python code using Elixir’s comprehensions and Enum pipelines!&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping: Enum.map and generators
&lt;/h2&gt;

&lt;p&gt;How can we write the step to square numbers? In Elixir, it’s simple!&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://hexdocs.pm/elixir/Enum.html#map/2" rel="noopener noreferrer"&gt;Enum.map&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and using comprehensions (&lt;code&gt;for&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;-&lt;/code&gt; represents a &lt;strong&gt;generator&lt;/strong&gt; expression, generating values to be used in the body of the &lt;code&gt;for&lt;/code&gt; expression, after &lt;code&gt;do:&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Filtering: Enum.filter and filters
&lt;/h2&gt;

&lt;p&gt;Easy to do with &lt;a href="https://hexdocs.pm/elixir/Enum.html#filter/2" rel="noopener noreferrer"&gt;Enum.filter&lt;/a&gt; (or &lt;a href="https://hexdocs.pm/elixir/Enum.html#reject/2" rel="noopener noreferrer"&gt;Enum.reject&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll want to filter out odd numbers before they are squared, so we place it in the right place in the pipeline - before &lt;code&gt;Enum.map&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using comprehensions, we can add a second expression to the head of the comprehension, a filter, which is a boolean test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;rem(n, 2) == 0&lt;/code&gt; expression then discards any elements that return &lt;code&gt;false&lt;/code&gt; (or &lt;code&gt;nil&lt;/code&gt;), leaving &lt;code&gt;[2, 4]&lt;/code&gt; as the numbers that are actually passed to the body (&lt;code&gt;do: n ** 2&lt;/code&gt;) of the comprehension.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reduce -&amp;gt; Enum.reduce and reduce:
&lt;/h2&gt;

&lt;p&gt;Using &lt;a href="https://hexdocs.pm/elixir/Enum.html#reduce/2" rel="noopener noreferrer"&gt;Enum.reduce/2&lt;/a&gt;, we can convert a list of squared numbers into their sum by adding to an accumulator. The first element is used as the initial value of the accumulator if we don’t specify an initial value for the accumulator (&lt;a href="https://hexdocs.pm/elixir/Enum.html#reduce/3" rel="noopener noreferrer"&gt;Enum.reduce/3&lt;/a&gt;), and that’s handy here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;&amp;amp;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With comprehensions, we have even more power than the Python equivalent. We can add a &lt;code&gt;reduce&lt;/code&gt; step by adding another clause to the head:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reduce:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;making two changes here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;adding a &lt;code&gt;reduce: 0&lt;/code&gt; clause to the head, to specify that we will accumulate a value whose initial value is 0&lt;/li&gt;
&lt;li&gt;changing the &lt;code&gt;for&lt;/code&gt; body to capture an &lt;code&gt;acc&lt;/code&gt; value (the accumulator) that we can add the current squared value to.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Built-in functions: Enum.sum
&lt;/h2&gt;

&lt;p&gt;As a general rule, we should express the data we want to transform in the highest-level way possible. It’s useful to think of &lt;code&gt;Enum.reduce&lt;/code&gt; as the lowest level functional transformation, since all other data processing can be rewritten in terms of it.&lt;/p&gt;

&lt;p&gt;The Enum module contains plenty of higher-level functions, typically involving reducing a list of values to a single aggregate value, like a &lt;a href="https://hexdocs.pm/elixir/Enum.html#sum/1" rel="noopener noreferrer"&gt;sum&lt;/a&gt;, &lt;a href="https://hexdocs.pm/elixir/Enum.html#max/3" rel="noopener noreferrer"&gt;maximum&lt;/a&gt; or &lt;a href="https://hexdocs.pm/elixir/Enum.html#min/3" rel="noopener noreferrer"&gt;minimum&lt;/a&gt;. In this case, we’d like the sum of the elements.&lt;/p&gt;

&lt;p&gt;For Enum pipelines, this is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is not a way to represent these high-level aggregate functions in comprehensions, so we can pipe the output of the comprehension into a &lt;code&gt;Enum.sum&lt;/code&gt; call like so, similar to how we did in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mixing different forms should generally be avoided, especially if the transformation is a simple one, as it results in less mental load for the reader - the &lt;code&gt;reduce:&lt;/code&gt; form above is actually clearer to read despite being lower-level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Elixir expression is better?
&lt;/h2&gt;

&lt;p&gt;To summarise, we’ve ended up with two forms which could be considered idiomatic. For Enum pipelines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and comprehensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reduce:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy to read code should be straightforward to scan through, without ambiguity or stumbling over expressions. I think both forms fill that criteria, as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;they follow a single consistent form - either &lt;code&gt;Enum&lt;/code&gt; pipelines or comprehensions&lt;/li&gt;
&lt;li&gt;each expression corresponds to a single processing step&lt;/li&gt;
&lt;li&gt;it can be read top-to-bottom or left-to-right without interruption&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Writing these transformations can be done in several different ways in Elixir, and it is easy for a codebase to vary styles, especially as code is changed and processing becomes more complicated over time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://puretype.ai" rel="noopener noreferrer"&gt;PureType&lt;/a&gt; can break down and analyze Enum pipelines and comprehensions to represent them in their clearest and most idiomatic form, learning your preferences and increasing your code’s readability and clarity for others on the team. Try it out today!&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>loops</category>
      <category>python</category>
      <category>functional</category>
    </item>
  </channel>
</rss>
