mrkurt

mrkurt

I’m reading through the type annotations features and trying to figure out how to enforce type checking in a Phoenix app. I’m pretty sure it has something to do with the dialyzer. :wink:

Given a project with functions like this, how can I type check it before releasing? The VS Code extension gives me convenient yellow squigglies when my annotations don’t map the derived types. It’s a start, but I want it to be more strict.

  @type presence_entry :: {any(), %{metas: list(%{ atom() => any() })}}
  @spec presence_by_region(list(presence_entry)) :: %{any() =>  non_neg_integer()}
  def presence_by_region(presence) do
    result = presence
              |> Enum.map(&(elem(&1,1)))
              |> Enum.flat_map(&Map.get(&1, :metas))
              |> Enum.filter(&Map.has_key?(&1, :region))
              |> Enum.group_by(&Map.get(&1, :region))
              |> Enum.sort_by(&(elem(&1, 0)))
              |> Map.new(fn {k,v}-> {k, length(v) } end)

    result
  end

Showing Posts 1 to 10

Adzz

Adzz

The thing to keep in mind is that fundamentally you can’t. Elixir is not typed, if you come from another typed language you should let go of that expectation or you will be disappointed.

HOWEVER, there are tools to help solve similar problems. Dialyzer is one, but it is not a static type checker, it does success typing. It’s important to know the difference, its limitations and what it aims to do.

Similarly, there are libraries like Norm and Hammox that can give you extra safety if you need it but again they approach the problem differently.

Finally if you want a statically typed language on the BEAM check out Gleam or Caramel.

Nicd

Nicd

This is actually Dialyzer. So what you get with the VSCode extension is pretty much as far as Dialyzer can go.

mrkurt

mrkurt OP

I’m fine with a build step. :smiley: I mean, I’d love a full static type system but annotations + static analysis seem like they could be enough.

axelson

axelson

Scenic Core Team

The general starting point would be to add dialyxir to your project:

https://github.com/jeremyjh/dialyxir/

That will give you a mix dialyzer task that you can use for CI to fail the build. Although if you don’t start this at the beginning of a project it can be quite difficult to add later because it can be very difficult to understand all the dialyzer errors, dialyzer is rather hamstrung since it has to rely on success typing.

dimitarvp

dimitarvp

If you are okay with runtime checks with meaningful error messages then using Domo, TypedStruct (which Domo uses) and EnumType have worked really well for me.

As for real compile-time static typing, so far there’s no such thing in the BEAM ecosystem although Facebook has announced that they are working on it.

baldwindavid

baldwindavid

Adding to the runtime options, I have been happy using TypeCheck (Guide/Readme — TypeCheck v0.13.7). The specs are written nearly the same as regular typespecs, so it feels familiar.

Beyond that, heavy use of function head pattern-matching and guards can get you a long way in enforcing correctness.

Adzz

Adzz

Except Gleam and Caramel, right?

ityonemo

ityonemo

I’ve taken a bit of a break from it (life is busy since I’m moving cities and I am architecting code at work) but I’m working on a compile-time static type checker for elixir. It should be more powerful/useful than dialyzer since it takes some stances on what types mean and it will have type literals and subtraction types.

It’s nowhere near there yet though, ha.

dimitarvp

dimitarvp

So I hear, never tried them still though.

mattludwigs

mattludwigs

I think maybe the spec could be:

# I am not sure what that first item is in the tuple but if it is an id I might
# make a type like: @type id() :: non_neg_integer() | String.t() assuming that
# maybe the id could be either of those types then I would replace the any() with
# id() in the presence_entry() type. 
@type presence_entry() :: {any(), Phoenix.Presence.presence()}

# I am going to make a new type here for the region, but I am making assumptions as I don't know the rest of the code and the type could be something else.
@type region() :: String.t()

@spec presence_by_region([presence_entry()]) :: %{region() => non_neg_integer()}

Dialyzer always assumes the most generic types possible, so since the code provided does not explicitly call anything on the any() it just assumes this can be anything, as the function will work if you pass anything into it for that parameter. I have found over the years that the more explicit you can be in telling Diazlyer your intentions the better it will type check across the codebase. That is why I recommend making a new type for the any()s - region() and id().

While Dialyzer isn’t very strict by default you can try to add a few flags to your Dialyzer configuration and just be explicit as possible. Plus I have found that being super explicit in types helps provide documentation on how the code is intended to work.

Another thing I might consider with this code is turning the presence entry into a struct and providing some function to make a Phoenix presence into this more known data structure (please note I am making a few assumptions here around naming, field, and types).

defmodule PresenceUser do

  @type id() :: non_neg_integer() | String.t()

  @type region() :: String.t()

  @type t() :: %__MODULE__{
          id: id(),
          region: region() | nil
        }

  defstruct id: nil, region: nil

  @spec from_presence_entry(presence_entry()) :: t()
  def from_presence_entry({id, presence}) do
    region = get_in(presence, [:metas, :region])
    %__MODULE__{id: id, region: region}
  end
end

## then the new spec for presence_by_region/1

@spec presence_by_region([PresenceUser.t()]) :: %{PresenceUser.region() => non_neg_integer()}

The exact names and types might be different depending on your codebase. I might even move the from_presence_entry/1 up a level in the API.

The reason I would do something like this is that not only is the code explicit to the reader, which is nice, it tells Dialyzer a lot of information, which as the codebase grows will help make Dialyzer seem more strict. However, I know that the community has various degrees of opinions on structs and Dialyzer. While this way of writing typed Elixir requires more typing I found it very useful over the years.

Here’s an example of the Dialyzer flags we set on most of our projects: vintage_net_mobile/mix.exs at main · nerves-networking/vintage_net_mobile · GitHub

— All posts loaded —

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

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
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
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews