<?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: Michał Szajbe</title>
    <description>The latest articles on DEV Community by Michał Szajbe (@szajbus).</description>
    <link>https://dev.to/szajbus</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%2F378030%2F1abbb85b-4f11-4171-800f-b5bd5e9e02e8.jpeg</url>
      <title>DEV Community: Michał Szajbe</title>
      <link>https://dev.to/szajbus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/szajbus"/>
    <language>en</language>
    <item>
      <title>Base64-encoded file uploads with Phoenix and Plug</title>
      <dc:creator>Michał Szajbe</dc:creator>
      <pubDate>Sat, 02 May 2020 11:05:45 +0000</pubDate>
      <link>https://dev.to/szajbus/base64-encoded-file-uploads-with-phoenix-and-plug-3011</link>
      <guid>https://dev.to/szajbus/base64-encoded-file-uploads-with-phoenix-and-plug-3011</guid>
      <description>&lt;p&gt;Handling external files is one of the basic tasks for many web applications. Profile pictures, attachments, resources fetched from remote services, all are examples of the above.&lt;/p&gt;

&lt;p&gt;The most common way such files find their way into a web app is by means of an HTTP multipart request, usually performed through a web page with an HTML form and a &lt;code&gt;file&lt;/code&gt; input tag or a client using app's HTTP API directly. Phoenix and, more specifically, Plug handles those very well, by wrapping the uploaded file information in a convenient struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Upload&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;content_type:&lt;/span&gt; &lt;span class="s2"&gt;"image/jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;filename:&lt;/span&gt; &lt;span class="s2"&gt;"rainbow.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;path:&lt;/span&gt; &lt;span class="s2"&gt;"/var/folders/nk/_hgjvryd2ml40g0jpyllt2080000gn/T//plug-1548/multipart-440190-911831-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Plug writes uploaded data on disk as a temporary file and provides its path along with original filename and type encapsulated in a struct. As such, it is passed to Phoenix controller with the rest of request params.&lt;/p&gt;

&lt;p&gt;The convenience of &lt;code&gt;Plug.Upload&lt;/code&gt; goes beyond the definition of the above struct. It also implements a &lt;code&gt;GenServer&lt;/code&gt; that tracks and manages all file uploads during the life cycle of the process &lt;sup id="fnref1"&gt;1&lt;/sup&gt;. This includes generation of unique file names, creation of intermediate directories in &lt;em&gt;tmp&lt;/em&gt; folder, creating and writing the file itself and, probably most importantly, cleanup of all the temporary files when the process terminates.&lt;/p&gt;

&lt;p&gt;However, not all the clients will find the HTTP multipart interface suitable. Instead of reimplementing mentioned &lt;code&gt;Plug.Upload&lt;/code&gt; functionality, it would be better to simply re-use it when handling files obtained by other means.&lt;/p&gt;

&lt;p&gt;Let's explore some options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Base64-encoded data
&lt;/h2&gt;

&lt;p&gt;XML and JSON are just two examples of text-based formats widely used in service-to-service communication. They are easy to implement and very flexible, but are limited to text only. Neither of those formats, nor the protocols that make use of them, directly support mixing with non-text data.&lt;/p&gt;

&lt;p&gt;The solution is to turn non-text (binary) data into plain text, that can be easily embedded in XML or JSON for transport and turned back into binary format at the destination. Base64 is probably the most common encoding scheme that helps to achieve this.&lt;/p&gt;

&lt;p&gt;Consider the following JSON document, describing some person and embedding her picture as Base64-encoded string (truncated for readability):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"john@example.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"picture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SGFuZGxpbmcgZXzEtMSIKfQpgYGA..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In contrast to HTTP multipart requests, this time Phoenix controller will receive &lt;code&gt;picture&lt;/code&gt; as string instead of &lt;code&gt;%Plug.Upload{}&lt;/code&gt; struct. To plug into the above-mentioned conveniences of &lt;code&gt;Plug.Upload&lt;/code&gt; we need to decode it and build the struct ourselves.&lt;/p&gt;

&lt;p&gt;First, let's define a helper function that writes arbitrary binary data into a temporary file and wraps it with a &lt;code&gt;%Plug.Upload{}&lt;/code&gt; struct &lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;binary_to_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Upload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"upload"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:binary&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
       &lt;span class="ss"&gt;:ok&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="ss"&gt;:ok&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&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="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Upload&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;path:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The important part is the call to &lt;code&gt;Plug.Upload.random_file/1&lt;/code&gt;, which creates a file in &lt;em&gt;tmp&lt;/em&gt; directory and sets up its tracking by the &lt;code&gt;GenServer&lt;/code&gt; so that it can be later cleaned up.&lt;/p&gt;

&lt;p&gt;Now the handling of Base64-encoded files is straightforward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;base64_to_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;binary_to_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Base64 encoding is quite common, but it's not the only alternative. Let's see another one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data URLs
&lt;/h2&gt;

&lt;p&gt;Mobile and JavaScript single page apps, even when using regular HTTP APIs, may prefer to upload files as &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs"&gt;Data URLs&lt;/a&gt; that include raw or Base64-encoded file contents.&lt;/p&gt;

&lt;p&gt;The format is very simple, it consists of &lt;code&gt;data:&lt;/code&gt; prefix, optional MIME type indicator, optional &lt;code&gt;base64&lt;/code&gt; token (for non-textual data) and the data itself. For example, &lt;code&gt;data:text/plain;base64,SGVsbG8gd29ybGQh&lt;/code&gt; is a &lt;code&gt;"Hello World!"&lt;/code&gt; string encoded as Data URL. You can try pasting it in your web browser's address bar and it should happily decode it.&lt;/p&gt;

&lt;p&gt;The approach here is analogous to the previous example - our goal is to decode the original file and plug it into &lt;code&gt;Plug.Upload&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The following code snippet uses &lt;a href="https://hex.pm/packages/ex_url"&gt;ex_url&lt;/a&gt; package, which provides parser and decoder for Data URLs (note &lt;code&gt;URL.Data.parse/1&lt;/code&gt; function).&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;data_url_to_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;scheme:&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;data:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;binary_to_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's see it in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_url_to_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data:text/plain;base64,SGVsbG8gd29ybGQh"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Upload&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;content_type:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;filename:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;path:&lt;/span&gt; &lt;span class="s2"&gt;"/var/folders/nk/_hgjvryd2ml40g0jpyllt2080000gn/T//plug-1548/base64-data-1548887956-746460942629208-8"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Looks good!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The essence of abstractions is preserving information that is relevant in a given context, and forgetting information that is irrelevant in that context.&lt;/p&gt;

&lt;p&gt;– John V. Guttag &lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Base64-encoded files may be a special case in your app, but with little work they can be handled as easily as regular files uploaded via HTML forms.&lt;/p&gt;

&lt;p&gt;Plug.Upload nicely abstracts away the process of handling temporary files. At the same time, it provides a convenient struct around them to work with.&lt;/p&gt;

&lt;p&gt;With it at your disposal, irrespective of where the external files come from, what format they're in and what your application does with them, you can rely on a universal, stable interface to ease both feature development and testing.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Wherever in this article a word &lt;em&gt;process&lt;/em&gt; is used, it refers to Erlang VM process. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;To keep the examples simple, I am not even trying to set &lt;code&gt;content_type&lt;/code&gt; and &lt;code&gt;filename&lt;/code&gt; fields in the structs. Depending on the situation they may or may not be available in the request data. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Guttag, John V. (2013-01-18). &lt;em&gt;Introduction to Computation and Programming Using Python&lt;/em&gt; (Spring 2013 ed.). Cambridge, Massachusetts: The MIT Press. ISBN 9780262519632. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
    </item>
    <item>
      <title>Understanding and fixing recompilation in Elixir projects</title>
      <dc:creator>Michał Szajbe</dc:creator>
      <pubDate>Sat, 02 May 2020 10:59:59 +0000</pubDate>
      <link>https://dev.to/szajbus/understanding-and-fixing-recompilation-in-elixir-projects-2ib2</link>
      <guid>https://dev.to/szajbus/understanding-and-fixing-recompilation-in-elixir-projects-2ib2</guid>
      <description>&lt;p&gt;Our goal as programmers is to deliver value by writing code. It should be efficient, maintainable, but most importantly correct.&lt;/p&gt;

&lt;p&gt;Correctness can be checked at multiple stages of the development process, but the most immediate feedback we can get is from the compiler, test suite or the REPL.&lt;/p&gt;

&lt;p&gt;We rarely write large chunks of code in one go, then test it as a whole, because that means unnecessary risk of wasted effort if it turns out not to work as expected. We’d rather make small, deliberate changes, then quickly test and adjust if needed before going forward.&lt;/p&gt;

&lt;p&gt;For that however, our feedback loops must be as short as possible. We strive for fast test suites and invest time in optimizing the compilation process, because they directly affect our workflow. Slow feedback contributes to lost focus at the very least.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Consider the rate of feedback as your speed limit. &lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When working with compiled languages, and Elixir is no exception here, we are destined to spend some time waiting for the compiler. And there may be times when even the smallest of changes result in recompilation of significant parts of the codebase. These situations quickly get annoying.&lt;/p&gt;

&lt;p&gt;Ever experienced this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;some/project/module.ex
&lt;span class="nv"&gt;$ &lt;/span&gt;mix &lt;span class="nb"&gt;test

&lt;/span&gt;Running tests...
Compiling 791 files &lt;span class="o"&gt;(&lt;/span&gt;.ex&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's a long wait before tests even run! Let’s find why and how to fix this.&lt;/p&gt;

&lt;h2&gt;
  
  
  How compiler decides what to recompile
&lt;/h2&gt;

&lt;p&gt;Elixir compiler uses &lt;a href="https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/kernel/lexical_tracker.ex"&gt;lexical tracker&lt;/a&gt; to track references to modules, function dispatches, usage of aliases, imports and requires in the code, etc. It uses this information to build project modules' dependency graph and ultimately optimize its own work.&lt;/p&gt;

&lt;p&gt;When module changes, the compiler finds its dependants by analyzing the dependency graph and marks them as stale. Next, dependants of these are marked as stale too. The process is repeated until the whole dependency graph is traversed and all the stale modules are identified.&lt;/p&gt;

&lt;p&gt;Whether a stale module will be recompiled depends on the type of dependency it has to a module that "made" it stale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding module dependencies
&lt;/h2&gt;

&lt;p&gt;Basically, when module &lt;code&gt;A&lt;/code&gt; uses module &lt;code&gt;B&lt;/code&gt; in any way, we say it depends on &lt;code&gt;B&lt;/code&gt;.&lt;br&gt;
Dependencies themselves are transitive. If &lt;code&gt;A&lt;/code&gt; depends on &lt;code&gt;B&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt; depends on &lt;code&gt;C&lt;/code&gt;, then by implication &lt;code&gt;A&lt;/code&gt; depends on &lt;code&gt;C&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;There are three types of dependencies between Elixir modules.&lt;/p&gt;
&lt;h5&gt;
  
  
  Compile-time dependencies
&lt;/h5&gt;

&lt;p&gt;Such dependecies are created when module &lt;code&gt;A&lt;/code&gt; uses module &lt;code&gt;B&lt;/code&gt; at compile time, for example by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requiring module &lt;code&gt;B&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;importing functions from &lt;code&gt;B&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;using macros from &lt;code&gt;B&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;delegating calls to &lt;code&gt;B&lt;/code&gt; via &lt;code&gt;defdelegate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;implementing behaviour &lt;code&gt;B&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;implementing protocol &lt;code&gt;B&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When &lt;code&gt;B&lt;/code&gt; changes, &lt;code&gt;A&lt;/code&gt; must be recompiled too.&lt;/p&gt;
&lt;h5&gt;
  
  
  Runtime dependencies
&lt;/h5&gt;

&lt;p&gt;Runtime dependencies happen when module &lt;code&gt;A&lt;/code&gt; interacts with &lt;code&gt;B&lt;/code&gt; only at runtime, e.g. by calling its functions (either by fully qualified name or via an alias).&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;B&lt;/code&gt; changes, &lt;code&gt;A&lt;/code&gt; does not need to be recompiled.&lt;/p&gt;
&lt;h5&gt;
  
  
  Struct dependencies
&lt;/h5&gt;

&lt;p&gt;This type of dependency is created when &lt;code&gt;A&lt;/code&gt; uses &lt;code&gt;%B{}&lt;/code&gt; struct.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;A&lt;/code&gt; needs to be recompiled only when the definition of &lt;code&gt;%B{}&lt;/code&gt; struct changes, because struct keys are checked at compile time. &lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Strategy for faster recompilation
&lt;/h3&gt;

&lt;p&gt;Actually it’s not about the speed of the compiler, but the amount of work it has to do. The less cross-dependencies in the codebase, the less modules will need to be recompiled after something changes.&lt;/p&gt;

&lt;p&gt;The obvious strategy would be to try to reduce compile-time dependencies, but reducing runtime and struct deps is equally important.&lt;/p&gt;

&lt;p&gt;Consider following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;A&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@answer&lt;/span&gt; &lt;span class="no"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;get_answer&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="nv"&gt;@answer&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;B&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;search&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="no"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;C&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;A&lt;/code&gt; has a compile-time dependency on &lt;code&gt;B&lt;/code&gt; because it calls a function from that module at compile-time (when module attributes are defined).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;B&lt;/code&gt; has only a runtime dependency on &lt;code&gt;C&lt;/code&gt; because it calls a function from that module at runtime.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;A&lt;/code&gt; doesn’t have a direct compile-time dependency on &lt;code&gt;C&lt;/code&gt;, but if &lt;code&gt;C&lt;/code&gt; changes, then &lt;code&gt;A&lt;/code&gt; &lt;strong&gt;must be recompiled&lt;/strong&gt;, even though &lt;code&gt;B&lt;/code&gt; doesn’t have to!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;lib/c.ex
&lt;span class="nv"&gt;$ &lt;/span&gt;mix compile &lt;span class="nt"&gt;--verbose&lt;/span&gt;
Compiling 2 files &lt;span class="o"&gt;(&lt;/span&gt;.ex&lt;span class="o"&gt;)&lt;/span&gt;
Compiled lib/c.ex
Compiled lib/a.ex
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The process is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compiler detects that &lt;code&gt;C&lt;/code&gt; changed (on disk), marks it as stale and for recompilation.&lt;/li&gt;
&lt;li&gt;Compiler sees that &lt;code&gt;B&lt;/code&gt; depends on something stale (&lt;code&gt;C&lt;/code&gt;), so marks it as stale as well, but doesn’t mark it for recompilation because it’s only a runtime dependency.&lt;/li&gt;
&lt;li&gt;Compiler sees that &lt;code&gt;A&lt;/code&gt; depends on something stale (&lt;code&gt;B&lt;/code&gt;), marks it as stale, but this time it’s a compile-time dependency, so the file is marked for recompilation too.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Clearly &lt;code&gt;A&lt;/code&gt; has an &lt;strong&gt;implicit&lt;/strong&gt; compile dependency on &lt;code&gt;C&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It was counter-intuitive to me at first &lt;sup id="fnref3"&gt;3&lt;/sup&gt;. It happens because the compiler assumes that &lt;code&gt;A&lt;/code&gt; &lt;em&gt;may&lt;/em&gt; be using &lt;code&gt;C&lt;/code&gt;’s code at compile time indirectly (through &lt;code&gt;B&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This fact is going to shape our strategy when trying to avoid recompilation hell in our projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing your project
&lt;/h3&gt;

&lt;p&gt;In large projects, it’s not uncommon to see cycles in the dependency graph. If there happen to be a compile-time dependency between member modules of such cycle, any change will trigger a cascade-style recompilation of other modules in that cycle as well as ones depending on them and so on.&lt;/p&gt;

&lt;p&gt;That’s why even changes that appear simple on the surface, sometimes get you few hundred files to recompile... and ruined workflow.&lt;/p&gt;

&lt;p&gt;As removing cycles from the dependency graph is rarely a trivial task, it's better to try to prevent them happening in the first place. &lt;sup id="fnref4"&gt;4&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Nonetheless, there are some dependency-breaking techniques listed below that may apply to any project.&lt;/p&gt;

&lt;h5&gt;
  
  
  As a prerequisite, get familiar with &lt;em&gt;xref&lt;/em&gt; tool.
&lt;/h5&gt;

&lt;p&gt;It’s built into &lt;code&gt;mix&lt;/code&gt; and can help you indentify super-connected modules in your project and modules that have deep subtrees of dependencies &lt;sup id="fnref5"&gt;5&lt;/sup&gt;. Simply use &lt;code&gt;mix help xref&lt;/code&gt; to start.&lt;/p&gt;

&lt;p&gt;There's also a short and practical overview of the tool written by Wojtek Mach on &lt;a href="https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects"&gt;Dashbit's blog&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Keep you library code clean
&lt;/h4&gt;

&lt;p&gt;There will probably be some well-connected modules in your business layer, like those in &lt;code&gt;User&lt;/code&gt; or &lt;code&gt;Account&lt;/code&gt; contexts. In general, business layer code is very likely to contain a lot of cross-module dependencies, even cycles.&lt;/p&gt;

&lt;p&gt;Library code on the other hand is supposed to be generic, it should not depend on any module from your business layer. Otherwise it would transfer all such dependencies to any place it’s referenced from.&lt;/p&gt;

&lt;p&gt;Here’s an example from actual project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# web/i18n.ex&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;I18n&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@moduledoc&lt;/span&gt; &lt;span class="sd"&gt;"""
  Internationalization with a gettext-based API.
  """&lt;/span&gt;

  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Gettext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;otp_app:&lt;/span&gt; &lt;span class="ss"&gt;:my_app&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;set_locale&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;locale:&lt;/span&gt; &lt;span class="n"&gt;locale&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="no"&gt;Gettext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&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;Pattern-matching on &lt;code&gt;%User{}&lt;/code&gt; struct creates a compile-time dependency on &lt;code&gt;User&lt;/code&gt; module. It didn't seem to be a big deal until we realized that it creates a lot of indirect dependencies throughout the whole project because &lt;code&gt;I18n&lt;/code&gt; module is very widely-used.&lt;/p&gt;

&lt;p&gt;A simple change yielded a huge positive change in recompilation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- def set_locale(%User{locale: locale}),
&lt;/span&gt;&lt;span class="gi"&gt;+ def set_locale(%{locale: locale}),
&lt;/span&gt;    do: Gettext.put_locale(__MODULE__, locale)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Don’t import everything
&lt;/h4&gt;

&lt;p&gt;In Phoenix apps, &lt;code&gt;web/router.ex&lt;/code&gt; builds a super-connected  &lt;code&gt;MyApp.Router.Helpers&lt;/code&gt; module. When you import it to use route helpers, you indirectly import a lot of its dependencies. To avoid that, alias it instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- import MyApp.Router.Helpers
&lt;/span&gt;&lt;span class="gi"&gt;+ alias MyApp.Router.Helpers, as: Routes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The same applies to any other well-connected module.&lt;/p&gt;

&lt;h4&gt;
  
  
  Change defdelegate to proxy functions
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;defdelegate&lt;/code&gt; defines functions via metaprogramming at compile time. Simple "proxy" functions would be runtime dependencies instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- defdelegate authorize(conn), to: Auth
&lt;/span&gt;&lt;span class="gi"&gt;+ def authorize(conn), do: Auth.authorize(conn)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Don’t define module attributes with remote functions
&lt;/h4&gt;

&lt;p&gt;Module attributes are defined at compile-time, if they are set by using remote functions, compile-time dependencies are created. If you don’t need to use module attributes in guards, consider functions instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- @extension_whitelist FileExt.images()
&lt;/span&gt;&lt;span class="gi"&gt;+ defp extension_whitelist, do: FileExt.images()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Use remote types in typespec
&lt;/h4&gt;

&lt;p&gt;Consider the example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Hello&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;username:&lt;/span&gt; &lt;span class="n"&gt;username&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="s2"&gt;"Hello, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;name&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="s2"&gt;"Hello, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;&lt;code&gt;Hello&lt;/code&gt; uses &lt;code&gt;%User{}&lt;/code&gt; and &lt;code&gt;%Admin{}&lt;/code&gt; structs,  so we have just struct dependencies, as shown by &lt;code&gt;xref&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mix xref graph
lib/hello.ex
├── lib/admin.ex &lt;span class="o"&gt;(&lt;/span&gt;struct&lt;span class="o"&gt;)&lt;/span&gt;
└── lib/user.ex &lt;span class="o"&gt;(&lt;/span&gt;struct&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now let’s add a pretty standard function &lt;code&gt;@spec&lt;/code&gt; that list these structs as accepted argument types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Suddenly, we get more strict compile-time deps &lt;sup id="fnref6"&gt;6&lt;/sup&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mix xref graph
lib/hello.ex
├── lib/admin.ex &lt;span class="o"&gt;(&lt;/span&gt;compile&lt;span class="o"&gt;)&lt;/span&gt;
└── lib/user.ex &lt;span class="o"&gt;(&lt;/span&gt;compile&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In order to fix this, we should rather define remote types and use them instead of structs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defstruct&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:username&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nv"&gt;@type&lt;/span&gt; &lt;span class="n"&gt;t&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="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Admin&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defstruct&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nv"&gt;@type&lt;/span&gt; &lt;span class="n"&gt;t&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="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Hello&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;Admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;username:&lt;/span&gt; &lt;span class="n"&gt;username&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="s2"&gt;"Hello, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;name&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="s2"&gt;"Hello, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Sometimes a quick, small change may result in removal of a crucial dependency and break a cycle in your dependency graph, yielding tangible improvements in recompilation speed. Other times, some improvements may come at the expense of code readability and understandability, and simply will not be worth it.&lt;/p&gt;

&lt;p&gt;Pay attention. The compiler, through slow recompilation, may be signalling problems in your code. It may prompt you to rethink your recent architectural decisions.&lt;/p&gt;

&lt;p&gt;Issues are generally easier and cheaper to fix when detected early and recompilation that's slowing down is a plainly visible warning sign you probably should take seriously.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;D. Thomas, A. Hunt. (2020). &lt;em&gt;The Pragmatic Programmer, 20th Anniversary Edition&lt;/em&gt;. Pearson Education, Inc. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;As of Elixir 1.6. See &lt;a href="https://github.com/elixir-lang/elixir/pull/6575"&gt;Separate tracking structs from compile-time dependencies #6575&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Until I received explanation from Jason Axelson. See &lt;a href="https://elixirforum.com/t/implicit-compile-time-dependencies/28988"&gt;Implicit compile-time dependencies&lt;/a&gt; in Elixir Forum. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;I personally look forward to using such tools as &lt;a href="https://github.com/sasa1977/boundary"&gt;boundary&lt;/a&gt; which makes cross-module dependencies explicit. Umbrella projects can also be helpful in that aspect as they don’t allow cyclic dependencies between individual apps. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;Technically it’s a graph, not a tree, but &lt;em&gt;xref&lt;/em&gt; displays it as such. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;I actually suspect this is a bug and plan to investigate it. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>elixir</category>
    </item>
  </channel>
</rss>
