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


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>As promised, here is the version with the proposed syntax, <em>keeping all of the existing clauses</em>:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule URI do
  @moduledoc """
  Utilities for working with URIs.
  """

  # Type aliases for better readability
  $ type scheme() = string() or nil
  $ type host() = string() or nil
  $ type port() = non_neg_integer() or nil
  $ type path() = string() or nil
  $ type query() = string() or nil
  $ type fragment() = string() or nil
  $ type userinfo() = string() or nil
  $ type uri_string() = string()
  $ type encoding_type() = :www_form or :rfc3986
  $ type query_map() = %{string() =&gt; string()}
  $ type query_enum() = enumerable.t()
  $ type query_pair() = {string(), string()}
  $ type predicate() = (byte() -&gt; boolean())
  $ type authority() = string() or nil

  @derive {Inspect, optional: [:authority]}
  defstruct [:scheme, :authority, :userinfo, :host, :port, :path, :query, :fragment]

  $ string() -&gt; port()
  def default_port(scheme) when is_binary(scheme), do: ...

  $ string(), port() and not nil -&gt; :ok
  def default_port(scheme, port) when is_binary(scheme) and is_integer(port) and port &gt;= 0, do: ...

  $ query_enum(), encoding_type() -&gt; string()
  def encode_query(enumerable, encoding \\ :www_form), do: ...

  $ query_pair(), encoding_type() -&gt; string()
  defp encode_kv_pair({key, _}, _encoding) when is_list(key), do: ...
  defp encode_kv_pair({_, value}, _encoding) when is_list(value), do: ...
  defp encode_kv_pair({key, value}, :rfc3986), do: ...
  defp encode_kv_pair({key, value}, :www_form), do: ...

  $ string(), map(), encoding_type() -&gt; %{}
  def decode_query(query, map \\ %{}, encoding \\ :www_form)
  def decode_query(query, %_{} = dict, encoding) when is_binary(query), do: ...
  def decode_query(query, map, encoding) when is_binary(query) and is_map(map), do: ...
  def decode_query(query, dict, encoding) when is_binary(query), do: ...

  $ string(), query_map(), encoding_type() -&gt; query_map()
  defp decode_query_into_map(query, map, encoding), do: ...

  $ string(), any(), encoding_type() -&gt; any()
  defp decode_query_into_dict(query, dict, encoding), do: ...

  $ string(), encoding_type() -&gt; enumerable.t()
  def query_decoder(query, encoding \\ :www_form) when is_binary(query), do: ...

  $ string(), encoding_type() -&gt; {query_pair(), string()} or nil
  defp decode_next_query_pair("", _encoding), do: ...
  defp decode_next_query_pair(query, encoding), do: ...

  $ string(), encoding_type() -&gt; string()
  defp decode_with_encoding(string, :www_form), do: ...
  defp decode_with_encoding(string, :rfc3986), do: ...

  $ byte() -&gt; boolean()
  def char_reserved?(character), do: ...

  $ byte() -&gt; boolean()
  def char_unreserved?(character), do: ...

  $ byte() -&gt; boolean()
  def char_unescaped?(character), do: ...

  $ string(), predicate() -&gt; string()
  def encode(string, predicate \\ &amp;char_unescaped?/1)
      when is_binary(string) and is_function(predicate, 1), do: ...

  $ string() -&gt; string()
  def encode_www_form(string) when is_binary(string), do: ...

  $ byte(), predicate() -&gt; string()
  defp percent(char, predicate), do: ...

  $ byte() -&gt; byte()
  defp hex(n) when n &lt;= 9, do: ...
  defp hex(n), do: ...

  $ string() -&gt; string()
  def decode(uri), do: ...

  $ string() -&gt; string()
  def decode_www_form(string) when is_binary(string), do: ...

  $ string(), string(), boolean() -&gt; string()
  defp unpercent(&lt;&lt;?+, tail::binary&gt;&gt;, acc, true), do: ...
  defp unpercent(&lt;&lt;?%, tail::binary&gt;&gt;, acc, spaces), do: ...
  defp unpercent(&lt;&lt;head, tail::binary&gt;&gt;, acc, spaces), do: ...
  defp unpercent(&lt;&lt;&gt;&gt;, acc, _spaces), do: ...

  $ byte() -&gt; byte() or nil
  defp hex_to_dec(n) when n in ?A..?F, do: ...
  defp hex_to_dec(n) when n in ?a..?f, do: ...
  defp hex_to_dec(n) when n in ?0..?9, do: ...
  defp hex_to_dec(_n), do: ...

  $ t() or uri_string() -&gt; {:ok, t()} or {:error, string()}
  def new(%URI{} = uri), do: ...
  def new(binary) when is_binary(binary), do: ...

  $ t() or uri_string() -&gt; t()
  def new!(%URI{} = uri), do: ...
  def new!(binary) when is_binary(binary), do: ...

  $ map() -&gt; t()
  defp uri_from_map(%{path: ""} = map), do: ...
  defp uri_from_map(map), do: ...

  $ t() or string() -&gt; t()
  def parse(%URI{} = uri), do: ...
  def parse(string) when is_binary(string), do: ...

  $ string() -&gt; query() or nil
  defp nilify_query("?" &lt;&gt; query), do: ...
  defp nilify_query(_other), do: ...

  $ string() -&gt; {authority(), userinfo(), host(), port()}
  defp split_authority(""), do: ...
  defp split_authority("//"), do: ...
  defp split_authority("//" &lt;&gt; authority), do: ...

  $ string() -&gt; string() or nil
  defp nilify(""), do: ...
  defp nilify(other), do: ...

  $ t() -&gt; string()
  def to_string(uri), do: ...

  $ t() or string(), t() or string() -&gt; t()
  def merge(uri, rel)
  def merge(%URI{scheme: nil}, _rel), do: ...
  def merge(_base, %URI{scheme: rel_scheme} = rel) when rel_scheme != nil, do: ...
  def merge(%URI{} = base, %URI{host: host} = rel) when host != nil, do: ...
  def merge(%URI{} = base, %URI{path: nil} = rel), do: ...
  def merge(%URI{host: nil, path: nil} = base, %URI{} = rel), do: ...
  def merge(%URI{} = base, %URI{} = rel), do: ...
  def merge(base, rel), do: ...

  $ path(), path() -&gt; path()
  defp merge_paths(nil, rel_path), do: ...
  defp merge_paths(_, "/" &lt;&gt; _ = rel_path), do: ...
  defp merge_paths(base_path, rel_path), do: ...

  $ path() -&gt; path()
  defp remove_dot_segments_from_path(nil), do: ...
  defp remove_dot_segments_from_path(path), do: ...

  $ string() -&gt; [string() or atom()]
  defp path_to_segments(path), do: ...

  $ [string() or atom()], [string() or atom()] -&gt; [string() or atom()]
  defp remove_dot_segments([], acc), do: ...
  defp remove_dot_segments([:/ | tail], acc), do: ...
  defp remove_dot_segments([_, :+ | tail], acc), do: ...
  defp remove_dot_segments(["."], acc), do: ...
  defp remove_dot_segments(["." | tail], acc), do: ...
  defp remove_dot_segments([".." | tail], [:/]), do: ...
  defp remove_dot_segments([".."], [_ | acc]), do: ...
  defp remove_dot_segments([".." | tail], [_ | acc]), do: ...
  defp remove_dot_segments([head | tail], acc), do: ...

  $ [atom() or string()] -&gt; string()
  defp join_reversed_segments([:/]), do: ...
  defp join_reversed_segments(segments), do: ...

  $ t(), string() -&gt; t()
  def append_query(%URI{} = uri, query) when is_binary(query) and uri.query in [nil, ""], do: ...
  def append_query(%URI{} = uri, query) when is_binary(query), do: ...

  $ t(), string() -&gt; t()
  def append_path(%URI{}, "//" &lt;&gt; _ = path), do: ...
  def append_path(%URI{path: path} = uri, "/" &lt;&gt; rest = all), do: ...
  def append_path(%URI{}, path) when is_binary(path), do: ...
end
</code></pre>
<p>I suggest that you also preserve and annotate all of the existing clauses, so we can effectively compare how existing code will be typed.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="367168" data-batch-url="/posts/batch_likers">
                        9
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/a-case-for-inline-type-annotations/71220/43">Post #42</a>
	                </div>
	            </div>
              <div id="likers-container-367168" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="367168"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

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


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>To me it both reads and works like <code>URI | (String | nil)</code>. But it is the same with or without parens.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="367174" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/a-case-for-inline-type-annotations/71220/44">Post #43</a>
	                </div>
	            </div>
              <div id="likers-container-367174" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="367174"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

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


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>It seems like inline type annotations–even if better–would be a more intrusive change to the language. You’re updating the core syntax of Elixir rather than introducing an optional header to each function.</p>
<p>I’m personally against making such a deep change to the language when the type system is still experimental and early stages.</p>
<p>And there are real downsides. For example, adding type information adds quite noise to the function signature (and seems like a few others feel in this thread the same way!). If you want “inline” typing, why not use pattern matching instead and let the compiler infer types from those patterns?</p>
<p>In my daily coding, I’m not constantly asking myself “what is the type of this variable”? It’s a secondary question when I’m reading code. I’d rather hover over a variable to understand its type than get overwhelmed with a function signature that is 2x the size.</p>
<p>Sure, I acknowledge there’s a downside to <code>integer() -&gt; integer()</code> syntax. I have to do an <code>Enum.zip</code> in my head to match the corresponding variables to the corresponding types. But can’t this be solved at the IDE level?</p>
<p>For example, in Typescript, I hover over a variable and it tells me the type.</p>
<p></p><div class="lightbox-wrapper"><a class="lightbox" href="https://forum.elixirforum.com/uploads/default/original/3X/8/2/82502234cc86fcc1af368400f9cba83109a1f40b.png" data-download-href="https://forum.elixirforum.com/uploads/default/82502234cc86fcc1af368400f9cba83109a1f40b" title="CleanShot 2025-06-13 at 11.16.15@2x" rel="nofollow"><img src="https://forum.elixirforum.com/uploads/default/original/3X/8/2/82502234cc86fcc1af368400f9cba83109a1f40b.png" alt="CleanShot 2025-06-13 at 11.16.15@2x" data-base62-sha1="iANKUxjTX2CMLZBO07sT27rS1Rh" width="690" height="179" data-dominant-color="232325"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">CleanShot 2025-06-13 at 11.16.15@2x</span><span class="informations">724×188 16.5 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div><p></p>
<p>Similarly, in Elixir we could show type hints on hover. It doesn’t have to be solved at the language level.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="367193" data-batch-url="/posts/batch_likers">
                        4
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/a-case-for-inline-type-annotations/71220/45">Post #44</a>
	                </div>
	            </div>
              <div id="likers-container-367193" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="367193"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

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


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Just curious, are all the parentheses needed for the <code>$</code> types? I think it’d be far more readable without them. Generally though this approach seems more readable than the inline types which imo work well for simple things but can become a monstrosity.</p>
<aside class="quote no-group" data-username="venkatd" data-post="45" data-topic="71220">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/venkatd/48/9419_2.png" class="avatar"> venkatd:</div>
<blockquote>
<p>If you want “inline” typing, why not use pattern matching instead and let the compiler infer types</p>
</blockquote>
</aside>
<p>Agree, let the compiler do the work. Taken to an extreme, it seems like specifying types may only be useful for external data at the boundary. If the compiler can make that a reality, then the out-of-band <code>$</code> types make even more sense as they’d be used selectively instead of throughout your codebase (unlike the other languages).</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="367217" data-batch-url="/posts/batch_likers">
                        4
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/a-case-for-inline-type-annotations/71220/46">Post #45</a>
	                </div>
	            </div>
              <div id="likers-container-367217" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="367217"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

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


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="DaAnalyst" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  DaAnalyst
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Maybe a separate topic, but I don’t think <code>nil</code> should be declared as part of a type alias (like ever). Instead it should find its place in the type spec above function headers (on case by case basis), because enabling something to be <code>nil</code> does not mean it can be of <code>any</code> type but instead permits a particular argument or return value to be undefined.</p>
<p>One instance that particularly bothers me in this regard can be found in the LiveView docs for <code>Phoenix.Component.attr/3</code> that reads:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">Note only `:any` and `:atom` expect the value to be set to `nil`.`
</code></pre>
<p>According to the above excerpt from the docs, for each attribute that I know for sure is of a particular type or module, but can also be undefined, I should define it as <code>:any</code>. IMO, this is plain wrong and I am violating this on purpose in pretty much every single function component.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="367218" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/a-case-for-inline-type-annotations/71220/47">Post #46</a>
	                </div>
	            </div>
              <div id="likers-container-367218" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="367218"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

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


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="venkatd" data-post="45" data-topic="71220">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/venkatd/48/9419_2.png" class="avatar"> venkatd:</div>
<blockquote>
<p>In my daily coding, I’m not constantly asking myself “what is the type of this variable”?</p>
</blockquote>
</aside>
<p>Me neither.  In fact I rarely read <code>@spec</code>s except for the odd hairy function.  In fact I used to highlight them as comments because for the most part I see them as pure noise (this broke and I haven’t bothered to fix it).  And yet, with clear variable names, pattern matching and guards, as well as low-bar good coding practices like not making “rabbit hole” function calls, I can always easily identify the types.</p>
<p>All that coupled with Elixir (likely/hopefully) getting type inference, there is absolutely no need for inline types.  We can use our modern editors to give us clarity in the times we’re confused.  As <a class="mention" href="/u/jam" rel="nofollow">@jam</a> says, let the compiler do the work!  I also don’t appreciate this rhetoric that inline types are “clearly superior” as it doesn’t lend to good faith debates.  In my mind, having them them on their own line is clearly superior. If you’re having trouble reconciling them then you’re stuffing way too much in your function head.</p>
<p>As far as “familiarity in the name of adoption” goes, I’m also not convinced of this.  Elixir is already different in many weird and wonderful ways.  For example, it makes the “obviously superior” decision to have separate syntax for blocks vs data structures (oh the horrors that poor overloaded curly brace is forced to endure in all those “modern” languages!)  This is to say that <em>anyone moving over to Elixir is already going to have to have an open mind or they’re going to have a very bad time</em> (as we see on this forum from time to time).  Adding inline types almost feels like a step towards the normalization of proposals to “make Elixir less like Elixir.”  If you want a “familiar” syntax on the BEAM, there is the fabulous Gleam project!  Though you’d be missing out on Elixir’s exceptional macro system meaning you’ll never have awesome projects like Ash.  I should say, though, that I have no idea what José’s adoption goals are, these are just my feelings.</p>
<p>That said, in the name of community and open ideas, I love that this is being entertained.  If it is found to be possible and this is what the community wants, then so be it.  But also, please no <img src="https://forum.elixirforum.com/images/emoji/apple/frowning.png?v=15" title=":frowning:" class="emoji" alt=":frowning:" loading="lazy" width="20" height="20"></p>
<hr>
<p><small>If we’re all going to be vibe coders by next year, does any of this even matter?  <img src="https://forum.elixirforum.com/images/emoji/apple/grin.png?v=15" title=":grin:" class="emoji" alt=":grin:" loading="lazy" width="20" height="20"> <img src="https://forum.elixirforum.com/images/emoji/apple/grimacing.png?v=15" title=":grimacing:" class="emoji" alt=":grimacing:" loading="lazy" width="20" height="20"> <img src="https://forum.elixirforum.com/images/emoji/apple/upside_down_face.png?v=15" title=":upside_down_face:" class="emoji" alt=":upside_down_face:" loading="lazy" width="20" height="20"></small></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="367229" data-batch-url="/posts/batch_likers">
                        4
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/a-case-for-inline-type-annotations/71220/48">Post #47</a>
	                </div>
	            </div>
              <div id="likers-container-367229" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="367229"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

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


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="35" data-topic="71220">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>For completeness, here is how the hostname function would be typed with our proposed types:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">$ URI.t() or binary() or nil -&gt; binary() or nil
def hostname(%URI{host: host}), do: host
def hostname(url) when is_binary(url), do: hostname(URI.parse(url))
def hostname(nil), do: nil
</code></pre>
</blockquote>
</aside>
<p>Doesn’t that imply different semantics from the Kotlin example? It is not obvious from the type signature that the return type is constrained by the argument types.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="367242" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/a-case-for-inline-type-annotations/71220/49">Post #48</a>
	                </div>
	            </div>
              <div id="likers-container-367242" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="367242"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

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


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I’m not sure the type system will go into clauses like dialyzer would do. The return type is <code>binary or nil</code> in all cases.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="367265" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/a-case-for-inline-type-annotations/71220/50">Post #49</a>
	                </div>
	            </div>
              <div id="likers-container-367265" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="367265"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

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


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Here’s a more comprehensive version with inline types that keeps all existing clauses:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule URI do
  @moduledoc """
  Utilities for working with URIs.
  """

  # Type aliases only when they add real clarity
  typealias EncodingType = :www_form | :rfc3986
  typealias QueryMap = %{String =&gt; String}
  typealias QueryPair = {String, String}
  typealias Predicate = (byte) -&gt; boolean
  typealias Segments = List of (String | atom)

  @derive {Inspect, optional: [:authority]}
  defstruct [
    scheme: String?,
    authority: String?,
    userinfo: String?,
    host: String?,
    port: non_neg_integer?,
    path: String?,
    query: String?,
    fragment: String?
  ]

  # All function clauses with clean inline types

  def default_port(scheme: String) -&gt; non_neg_integer? when is_binary(scheme)

  def default_port(scheme: String, port: non_neg_integer) -&gt; :ok 
    when is_binary(scheme) and is_integer(port) and port &gt;= 0

  def encode_query(enumerable: Enumerable of QueryPair, encoding: EncodingType \\ :www_form) -&gt; String

  defp encode_kv_pair({key, _}: QueryPair, _encoding: EncodingType) -&gt; no_return when is_list(key)
  defp encode_kv_pair({_, value}: QueryPair, _encoding: EncodingType) -&gt; no_return when is_list(value)
  defp encode_kv_pair({key, value}: QueryPair, :rfc3986) -&gt; String
  defp encode_kv_pair({key, value}: QueryPair, :www_form) -&gt; String

  def decode_query(query: String, map: QueryMap \\ %{}, encoding: EncodingType \\ :www_form) -&gt; QueryMap
  def decode_query(query: String, %_{} = dict: any, encoding: EncodingType) -&gt; any when is_binary(query)
  def decode_query(query: String, map: QueryMap, encoding: EncodingType) -&gt; QueryMap 
    when is_binary(query) and is_map(map)
  def decode_query(query: String, dict: any, encoding: EncodingType) -&gt; any when is_binary(query)

  defp decode_query_into_map(query: String, map: QueryMap, encoding: EncodingType) -&gt; QueryMap

  defp decode_query_into_dict(query: String, dict: any, encoding: EncodingType) -&gt; any

  def query_decoder(query: String, encoding: EncodingType \\ :www_form) -&gt; Enumerable of QueryPair 
    when is_binary(query)

  defp decode_next_query_pair("", _encoding: EncodingType) -&gt; nil
  defp decode_next_query_pair(query: String, encoding: EncodingType) -&gt; {QueryPair, String}?

  defp decode_with_encoding(string: String, :www_form) -&gt; String
  defp decode_with_encoding(string: String, :rfc3986) -&gt; String

  def char_reserved?(character: byte) -&gt; boolean

  def char_unreserved?(character: byte) -&gt; boolean

  def char_unescaped?(character: byte) -&gt; boolean

  def encode(string: String, predicate: Predicate \\ &amp;char_unescaped?/1) -&gt; String
    when is_binary(string) and is_function(predicate, 1)

  def encode_www_form(string: String) -&gt; String when is_binary(string)

  defp percent(char: byte, predicate: Predicate) -&gt; String

  defp hex(n: byte) -&gt; byte when n &lt;= 9
  defp hex(n: byte) -&gt; byte

  def decode(uri: String) -&gt; String

  def decode_www_form(string: String) -&gt; String when is_binary(string)

  defp unpercent(&lt;&lt;?+, tail: binary&gt;, acc: String, true) -&gt; String
  defp unpercent(&lt;&lt;?%, tail: binary&gt;, acc: String, spaces: boolean) -&gt; String
  defp unpercent(&lt;&lt;head, tail: binary&gt;, acc: String, spaces: boolean) -&gt; String
  defp unpercent(&lt;&lt;&gt;&gt;, acc: String, _spaces: boolean) -&gt; String

  defp hex_to_dec(n: byte) -&gt; byte? when n in ?A..?F
  defp hex_to_dec(n: byte) -&gt; byte? when n in ?a..?f
  defp hex_to_dec(n: byte) -&gt; byte? when n in ?0..?9
  defp hex_to_dec(_n: byte) -&gt; nil

  def new(%URI{} = uri) -&gt; {:ok, t}
  def new(binary: String) -&gt; {:ok, t} | {:error, String} when is_binary(binary)

  def new!(%URI{} = uri) -&gt; t
  def new!(binary: String) -&gt; t when is_binary(binary)

  defp uri_from_map(%{path: ""} = map) -&gt; t
  defp uri_from_map(map) -&gt; t

  def parse(%URI{} = uri) -&gt; t
  def parse(string: String) -&gt; t when is_binary(string)

  defp nilify_query("?" &lt;&gt; query: String) -&gt; String
  defp nilify_query(_other: any) -&gt; nil

  defp split_authority("") -&gt; {nil, nil, nil, nil}
  defp split_authority("//") -&gt; {String?, nil, String, nil}
  defp split_authority("//" &lt;&gt; authority: String) -&gt; {String?, String?, String?, non_neg_integer?}

  defp nilify("") -&gt; nil
  defp nilify(other: any) -&gt; any

  def to_string(uri: t) -&gt; String

  def merge(base_uri: t | String, relative_uri: t | String) -&gt; t
  def merge(%URI{scheme: nil} = uri, _rel: t | String) -&gt; no_return
  def merge(base: t | String, %URI{scheme: rel_scheme} = rel) -&gt; t when rel_scheme != nil
  def merge(%URI{} = base, %URI{host: host} = rel) -&gt; t when host != nil
  def merge(%URI{} = base, %URI{path: nil} = rel) -&gt; t
  def merge(%URI{host: nil, path: nil} = base, %URI{} = rel) -&gt; t
  def merge(%URI{} = base, %URI{} = rel) -&gt; t
  def merge(base: String, rel: String) -&gt; t

  defp merge_paths(base_path: nil, rel_path: String?) -&gt; String?
  defp merge_paths(base_path: String?, "/" &lt;&gt; _ = rel_path: String?) -&gt; String?
  defp merge_paths(base_path: String?, rel_path: String?) -&gt; String?

  defp remove_dot_segments_from_path(path: nil) -&gt; nil
  defp remove_dot_segments_from_path(path: String) -&gt; String

  defp path_to_segments(path: String) -&gt; Segments

  defp remove_dot_segments([]: Segments, acc: Segments) -&gt; Segments
  defp remove_dot_segments([:/ | tail]: Segments, acc: Segments) -&gt; Segments
  defp remove_dot_segments([_, :+ | tail]: Segments, acc: Segments) -&gt; Segments
  defp remove_dot_segments(["."]: Segments, acc: Segments) -&gt; Segments
  defp remove_dot_segments(["." | tail]: Segments, acc: Segments) -&gt; Segments
  defp remove_dot_segments([".." | tail]: Segments, [:/]: Segments) -&gt; Segments
  defp remove_dot_segments([".."]: Segments, [_ | acc]: Segments) -&gt; Segments
  defp remove_dot_segments([".." | tail]: Segments, [_ | acc]: Segments) -&gt; Segments
  defp remove_dot_segments([head | tail]: Segments, acc: Segments) -&gt; Segments

  defp join_reversed_segments(segments: [:/]) -&gt; String
  defp join_reversed_segments(segments: Segments) -&gt; String

  def append_query(%URI{} = uri, query: String) -&gt; t 
    when is_binary(query) and uri.query in [nil, ""]
  def append_query(%URI{} = uri, query: String) -&gt; t when is_binary(query)

  def append_path(uri: t, "//" &lt;&gt; _ = path) -&gt; no_return
  def append_path(%URI{path: path} = uri, "/" &lt;&gt; rest = all: String) -&gt; t
  def append_path(uri: t, path: String) -&gt; no_return when is_binary(path)
end

defimpl String.Chars, for: URI do
  def to_string(uri: URI.t()) -&gt; String

  defp extract_authority(%URI{host: nil, authority: authority}) -&gt; String?
  defp extract_authority(%URI{host: host, userinfo: userinfo, port: port}) -&gt; iodata
end
</code></pre>
<h1><a name="p-367272-leveraged-improvements-1" class="anchor" href="#p-367272-leveraged-improvements-1" aria-label="Heading link" rel="nofollow"></a>Leveraged Improvements:</h1>
<h3><a name="p-367272-inline-type-syntax-can-be-changed-to-if-necessary-2" class="anchor" href="#p-367272-inline-type-syntax-can-be-changed-to-if-necessary-2" aria-label="Heading link" rel="nofollow"></a>Inline Type Syntax (can be changed to <code>::</code> if necessary)</h3>
<pre data-code-wrap="elixir"><code class="lang-elixir"># Parameters use single colon (:) with no spaces
def encode_query(enumerable: Enumerable of QueryPair, encoding: EncodingType) -&gt; String

# Return types use arrow (-&gt;)
def default_port(scheme: String) -&gt; non_neg_integer?
</code></pre>
<h3><a name="p-367272-optional-types-with-3" class="anchor" href="#p-367272-optional-types-with-3" aria-label="Heading link" rel="nofollow"></a>Optional Types with <code>?</code></h3>
<pre data-code-wrap="elixir"><code class="lang-elixir"># Instead of: String.t() | nil
def default_port(scheme: String) -&gt; non_neg_integer?

# In struct definitions
defstruct [
  scheme: String?,
  host: String?,
  port: non_neg_integer?
]
</code></pre>
<h3><a name="p-367272-collection-type-constraints-with-of-4" class="anchor" href="#p-367272-collection-type-constraints-with-of-4" aria-label="Heading link" rel="nofollow"></a>Collection Type Constraints with <code>of</code></h3>
<pre data-code-wrap="elixir"><code class="lang-elixir"># Clear collection contents without full generics
def query_decoder(query: String) -&gt; Enumerable of QueryPair
typealias Segments: List of (String | atom)
</code></pre>
<h3><a name="p-367272-type-inference-from-patterns-5" class="anchor" href="#p-367272-type-inference-from-patterns-5" aria-label="Heading link" rel="nofollow"></a>Type Inference from Patterns</h3>
<pre data-code-wrap="elixir"><code class="lang-elixir"># Struct patterns - type obvious from %URI{} match
def merge(%URI{} = base, %URI{} = rel) -&gt; t
def new(%URI{} = uri) -&gt; {:ok, t}

# Literal patterns - type obvious from literal value  
defp split_authority("") -&gt; {nil, nil, nil, nil}
defp split_authority("//") -&gt; {String?, nil, String, nil}
defp hex_to_dec(_n: byte) -&gt; nil
</code></pre>
<h3><a name="p-367272-improved-struct-definitions-6" class="anchor" href="#p-367272-improved-struct-definitions-6" aria-label="Heading link" rel="nofollow"></a>Improved Struct Definitions</h3>
<pre data-code-wrap="elixir"><code class="lang-elixir"># DRY: types defined once in defstruct, not separately in @type
defstruct [
  scheme: String?,
  authority: String?,
  userinfo: String?,
  host: String?,
  port: non_neg_integer?,
  path: String?,
  query: String?,
  fragment: String?
]
# @type t automatically generated
</code></pre>
<h3><a name="p-367272-consistent-typealias-syntax-7" class="anchor" href="#p-367272-consistent-typealias-syntax-7" aria-label="Heading link" rel="nofollow"></a>Consistent Typealias Syntax</h3>
<pre data-code-wrap="elixir"><code class="lang-elixir">typealias EncodingType = :www_form | :rfc3986     # Union type
typealias QueryPair =  {String, String}             # Semantic meaning, contained complexity
</code></pre>
<h3><a name="p-367272-simplified-type-names-8" class="anchor" href="#p-367272-simplified-type-names-8" aria-label="Heading link" rel="nofollow"></a>Simplified Type Names</h3>
<pre data-code-wrap="elixir"><code class="lang-elixir"># Sugar makes using Types more concise
String?          # not String.t() | nil
Enumerable      # not Enumerable.t()
</code></pre>
<p>Collectively, these constructs achieve:</p>
<ul>
<li><strong>Locality</strong>: Types next to parameters they describe</li>
<li><strong>Familiarity</strong>: Syntax similar to Swift, TypeScript, Rust, Kotlin</li>
<li><strong>Conciseness</strong>: <code>?</code> sugar, pattern inference, <code>of</code> constraints</li>
<li><strong>Consistency</strong>: Single <code>:</code> for all type annotations, <code>-&gt;</code> for returns</li>
<li><strong>Maintainability</strong>: DRY struct definitions, smart typealias usage</li>
<li><strong>Readability</strong>: Clear without being verbose</li>
</ul>
<p>In comparison to <code>$</code></p>
<ul>
<li><strong>No mental mapping</strong> of positional types to parameters</li>
<li><strong>Self-documenting</strong> function signatures</li>
<li><strong>Easier refactoring</strong> - types move with parameters</li>
<li><strong>Better IDE support</strong> potential for inline type information</li>
<li><strong>Pattern-aware</strong> - leverages Elixir’s existing pattern matching strengths</li>
</ul> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="367272" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/a-case-for-inline-type-annotations/71220/51">Post #50</a>
	                </div>
	            </div>
              <div id="likers-container-367272" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="367272"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

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


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>At the risk of sounding sarcastic, if we are genuinely considering the “function juggling” argument then we’re likely going to want to also consider the following scenarios:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">@doc [foo: 2], """
Do a foo.
"""
def foo(a, b), do: a + b
</code></pre>
<pre data-code-wrap="elixir"><code class="lang-elixir">@tag "writes to the given file", :tmp_dir
test "writes to the given file", %{tmp_dir: tmp_dir} do
  # ...
end
</code></pre>
<pre data-code-wrap="elixir"><code class="lang-elixir">attr :avatar, :user, User, required: true
slot :avatar, :inner_block

def avatar(assigns) do
  ~H"""
  &lt;%!-- --&gt;
  """
end
</code></pre>
<p>I actually do think that:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def foo do
  @doc "I'm a foo that does foo things!"
  "foo"
end
</code></pre>
<p>would be sorta be nice as it is consistent with how <code>@moduledoc</code> works.  I’m sure there was a good reason it wasn’t done this way but it is something I think about sometimes when I write a <code>@doc</code>.  I’m pretty happy with status quo, though.</p>
<p>Also, if you use vanilla Vim you can use my <a href="https://github.com/sodapopcan/vim-mixer/" rel="noopener nofollow ugc">mixer.vim</a> plugin which has a text object that includes all function heads with all and their annotations (including <code>attr</code> and <code>slot</code>)—refactoring woes solved! <img src="https://forum.elixirforum.com/images/emoji/apple/stuck_out_tongue.png?v=15" title=":stuck_out_tongue:" class="emoji" alt=":stuck_out_tongue:" loading="lazy" width="20" height="20">    Figured I’d may as well include some self-promo at this point <img src="https://forum.elixirforum.com/images/emoji/apple/grinning_cat.png?v=15" title=":grinning_cat:" class="emoji" alt=":grinning_cat:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="367278" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/a-case-for-inline-type-annotations/71220/52">Post #51</a>
	                </div>
	            </div>
              <div id="likers-container-367278" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="367278"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

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