smueller

smueller

With the announcement of 1.19 rc0 and the path to user-supplied type annotations, I want to make the case for inline types—both for function parameters and local variables—rather than the currently implied direction of out-of-band annotation signatures using a $ prefix. The examples shared so far suggest something like:

$ integer() -> integer()
def inc(i), do: i + 1

This pattern – separating types from function heads – is unusual and, in my opinion, a step backwards compared to almost every modern language that supports types.

Why Inline Type Annotations Matter

Clarity and Locality

Types belong next to the values they describe. Splitting types from the function definition forces developers to mentally reconcile two separate lines:

# Inline (clear and self-contained)
def inc(i: integer): integer do
  i + 1
end

# Out-of-band (disconnected)
$ integer() -> integer()
def inc(i), do: i + 1

Inline syntax offers immediacy—everything needed to understand the function is in one place.

Developer Familiarity

Languages like TypeScript, Swift, Kotlin, Rust, C#, and even Python (via PEP 484) all use inline typing for a reason. It’s readable, discoverable, and familiar to modern developers. A $ line above each function feels more like a callback to the days of @spec, and misses the opportunity to modernize.

Better Tooling & UX

Inline types play better with IDEs and editors. They improve autocomplete, refactoring support, inlay hints, and inline docs. Detached signatures complicate tooling and reduce feedback during development.


Variable Type Annotations Are Just as Important

It’s not just function signatures. We need a way to annotate local variables inline:

x: integer = some_potentially_dynamic_value()

case y: String.t() <- some_api_call() do
  ...
end

Other languages with gradual typing (Python, TypeScript, Julia) allow inline variable annotations. Without this, dynamic code within functions remains opaque to tools and reviewers, limiting the power of gradual typing.

Obviously retrofitting types on elixir is complex, and everyone appreciates the care being taken by José and the core team. But inline types—both for function signatures and variables—are essential if Elixir wants to deliver a type system that feels natural, modern, and ergonomic.

Would love to hear thoughts from others, and to also hear from the core team why it appears that $ annotations have so much momentum already!

Showing Posts 1 to 10

plcholder

plcholder

Interesting, I have no authority on type theory, I had a similar thought also. I do wonder how would inline work with say a complex pattern match it may look a bit too noisy versus the out-of-band look, you make a good point with dx is it possible both can be implemented and one is just syntax sugar of the other etc?

felix-starman

felix-starman

There’s an extremely long thread about this from when this was first discussed, and the main reason is for backwards compatibility and the ability to undo it if the community decides they don’t like it.

Doing it inline would basically require it to be a 2.0 bump and make it difficult to “try it out” or reverse course.

With the current approach, once type annotations are fully supported, you could implement your own inline approach and if people liked it, adopt it, and maybe the language could evolve that way anyway, but there’d be more flexibility to try different things out along the way

cmo

cmo

What they said :up_arrow:

We do not need a way to annotate local variables. You might want one though. We already have when is_binary(y) and the compiler will hopefully know what type the functions return.

Do you want to be able to type a value to integer when the type system has determined it to be dynamic?

smueller

smueller OP

I haven’t seen that thread, but the logic seems completely unintuitive. Inline types aren’t some new breakout syntax that’s never been tried before – we’re talking about 50 years of programming languages new and old that have consistently proven (and chosen) the best thing. Why sacrifice so many benefits for the optionality of backing out due to community backlash? That’s just mitigating the wrong risks, while being indecisive at the same time.

If it’s significantly harder to implement the more proven and established way (inline typing), then the team should just say that. There would likely be more support for the annotated $ approach.

smueller

smueller OP

Interesting choice of terminology to quibble – why not go further and ask whether we need any type system at all?

See the other recent thread about perspective on Rust / Kotlin / Scala developers and whether they would be compelled to switch. https://forum.elixirforum.com/t/do-you-think-the-new-type-system-would-be-enough-to-attract-folks-that-are-also-interested-in-ocaml-haskell-and-scala

The direction that’s being advocating in this post is focused on building great language ergonomics and compiler trust, and the downstream effect is ultimately more elixir adoption, instead of continuing to be confined as a niche language

IloSophiep

IloSophiep

Let me start by saying i also thought that people are free to try out macros to inline type definitions if they want to experiment with that. You can always go from one style to the other with Elixir? No reason to think it’s set in stone i think?

But i work with the language Elm for fun every now and then which uses separate types, but at work i use C#, Typescript and sometimes even Pascal / Delphi. And i feel like the differences are not as crazy as you point them out to be? In Elm you have types for “variables” (just because it’s basically the same as a function) and often you only need to read the type of a function to get the gist of it (which is very elixir-like i think):

some_value : Int
some_value =
  42

stringify : Int -> String
stringify value =
  String.fromInt value

generate : ShoppingCart -> Account -> Invoice
generate cart account =
  ...

I don’t really see how the “clarity” of one syntax is objectively better. I would agree that there is probably more “familiarity” with the inline-style, but there a quite a few languages with seperated types that are closer to Elixir than e.g. Python is, i think? And on top of that the pattern matching in function heads and variable assignments are not something that’s very common in any of the languages you listed as “let’s look at them”, so there is more stuff going on in Elixir function heads already, i think? “Locality” and “tooling” i can only judge from using Elm and i think the Elm compiler is really good and i would rather spend my time 2h refactoring with the Elm tooling than 10min refactoring with C# tooling. I don’t see the reason why a different notation of types can make the tooling so much worse?

Sorc96

Sorc96

While there is personal preference to separate/inline type annotations, I don’t think they make a difference for the tooling. There’s no reason why IDEs should have a harder time with separate signatures.

Personally, I like inline type signatures, but having them together with non-trivial pattern matching could be very difficult to understand. Even in TypeScript, function heads can get pretty confusing, especially when some parameters are themselves functions.

I’m not sure how much value there is to explicitly typing local variables. Your examples are already handled by guards in pattern matching. And the compiler would need to generate those guards anyway, since the type check has to happen at runtime.

Finally, Elixir not having a static type system has always seemed like a red herring to me when it comes to reasons for not switching. Many of the most innovative and influential software developers have consistently gravitated towards dynamically typed languages, like various forms of LISP, Smalltalk, Erlang or even Ruby.

I know that the church of the static type has gained a lot of zealous followers on the internet, but there is always a trade off, no matter how much people like to pretend otherwise. In the end, somebody will necessarily be disappointed by some aspect of the type system, whether it will be the syntax, its capabilities or even its existence. There’s just no way to please everybody.

And then, even when the perfect type system has been implemented for Elixir, people will still refuse to switch. Because it was never really about types, but about people using what they know and not understanding that there is something actually different from the other technologies that all do the same things with different syntax.

dimitarvp

dimitarvp

I have met some of these people. Dynamic languages for them are a way to start doing stuff immediately. They view statically typed PLs as a hassle that’s always getting in the way. They want to get on with it.

I sympathize with that, a lot. But I feel that this mindset being brought to the work place is a very thin line to tread: one between “let’s deliver this in a reasonable timeframe” and a good old impatience. The former I respect. The latter: I don’t. Sprinting past proper tech due diligence is one of the best ways to accumulate tech debt.

Paying tech debt is not fun. It’s often extremely difficult, too, and I have witnessed it killing commercial projects.

All that being said, Elixir strikes a very good balance. But as your codebase and complexity grows, lack of static typing becomes more and more source of development slowdowns. Even that can be mitigated somewhat… but it does require paying the tech debt periodically. Which almost nobody wants to do.

plcholder

plcholder

Well said, static types save cost in the long run, they can help set the programmers intentions with the right invariant’s. I love writing elixir, but i always have that weird feeling that I wrote something wrong somewhere, that will comeback to bite me maybe not now, maybe in a few weeks, years etc ofc BEAM idiom of let it crash, does mitigate a bit of the worries but it is still a far cry from something like rust were you can guarantee things hold in most cases & you don’t need to write insane conditionals just to make sure some input is the right type

lud

lud

It’s a matter of taste maybe. In Haskell you have the annotations on top of the functions and I like it.

Elixir supports multiple clauses, so from that function:

def hostname(%URI{host: host}), do: host
def hostname(url) when is_binary(url), do: hostname(URI.parse(url))
def hostname(nil), do: nil

You could get something like this:

def hostname(%URI{host: host} :: URI.t()) :: String.t() | nil, do: host
def hostname(url :: binary()) when is_binary(url) :: String.t() | nil, do: hostname(URI.parse(url))
def hostname(nil :: nil) :: nil, do: nil

And I think it’s pretty unreadable.

Or maybe the type system will not collect all clauses (I’m sure it will, but it may not collect all clauses declarations), so you may have to do something like this:

def hostname(%URI{host: host} :: URI.t() | String.t() | nil) :: String.t() | nil, do: host
def hostname(url :: URI.t() | String.t() | nil) when is_binary(url) :: String.t() | nil, do: hostname(URI.parse(url))
def hostname(nil :: URI.t() | String.t() | nil) :: String.t() | nil, do: nil

Let’s simplify a bit:

$ has_hostname :: URI.t() | String.t() | nil
$ maybe_hostname :: String.t() | nil
def hostname(%URI{host: host} :: has_hostname) :: maybe_hostname, do: host
def hostname(url :: has_hostname) when is_binary(url) :: maybe_hostname, do: hostname(URI.parse(url))
def hostname(nil :: has_hostname) :: maybe_hostname, do: nil

But really my favorite is to separate the thing with a function head:

def hostname(uri :: URI.t() | String.t() | nil) :: String.t() | 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

Note that I chose to use the function head syntax and the :: operator because if we support types only in function heads for now, and if it gets adopted and there is no going back, then we can allow it later in actual function clauses, especially when there is only one clause.

With $ + arrows well it’s a different syntax so it may be harder to inline those in functions, but I guess it’s feasible.

My point is that a type system needs to see functions as a whole ('cause you cannot chose a specific function clause to call from the outside – by other matters than passing the right arguments of course).

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 94592 917
New
cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
axelson
Hi there! :wave: @frigidcode and I (but mostly him) have been running an Elixir Book club, we’re almost done with Designing Elixir Syste...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
georgeguimaraes
Just published claude-code-elixir, a plugin marketplace for Claude Code with Elixir support. These are the plugins I’ve been using for my...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews