IvanR

IvanR

There is one long term request - to have strict type checking in Elixir.
As far as I remember, @rcm765 mentioned that in the talk at Lonestar ElixirConf 2019. And after reading the How to make Dialyzer more strict? it’s clear that dialyzer is good to have and at the same time it’s not enough to check all possible function calls automatically. And eventually, there is no “one-button” tool to verify the data consistency throughout the app at the compile-time.
We have no Haskel like type inference with dialyzer types, that seems required to do that. And according to @josevalim, it seems impossible to introduce a native type system into the Elixir language itself without breaking compatibility with existing applications code.
There are several projects for building a compile-time type-safe BEAM language from the ground-up like Alpaca, Gleam, and some others Statically typed languages on BEAM. Same time, integration with other BEAM running software can be an issue according to @rvirding.
There are Elixir libraries f.e. ExType by @gyson, and proof of concepts like TypedElixir and MLElixir by @OvermindDL1. These seem are for making the strict equivalent of the dialyzer tool.
It looks like a not too optimistic situation.

It’d be good to look at the problem from a different angle. For what we need strict types when writing an application?
Usually, to check the consistency of states of business data, we operate in the app.
To model the data, we make structures, take out values from them, process them, and assemble back.
Here is an idea - we can match the value type with defined field type each time we assemble a struct at run-time, and raise or return an error on mismatch. And to differentiate the same typed values by the business meaning, we can wrap them into tagged tuples. That is, to keep the link of the separated value to the concrete struct with a tuple’s tag.
With means of the Domo library, that I’m glad to present, it’s expressed like the following.

defmodule Order do
  use Domo

  alias Measure.{Kilograms, Units}

  deftag Id, for_type: String.t()
  deftag Quantity, for_type: Kilograms.t() | Units.t()
  deftag Note, for_type: :none | String.t()

  typedstruct do
    field :id, Id.t()
    field :quantity, Quantity.t()
    field :note, Note.t(), default: Note --- :none
  end
end

Order.new!(
  id: Id --- "156",
  quantity: Quantity --- Kilograms --- 2.5
  note: Note --- "Deliver on Tue"
)

The dialyzer can check the correctness of data assembly in the context of struct-value relation at the compile-time. At the run-time, the autogenerated new!/3, put!/3, and merge!/3 functions of the struct automatically matches values themselves against field type.

In combination with the expat library by @vic , the processing of the tags can be neat. Domo is fully compatible with the existing Elixir code bases and can be introduced in existing projects gracefully. More examples are in the Domo library’s documentation (the master version of Elixir is necessary to run). There is an example app included in the library’s repo.

Folks, is it an absurd idea to ask you to give feedback on the approach presented, and how it can be made even better?

Showing Posts 1 to 10

timCF

timCF

Add proper encapsulation) I had some fun with solving similar issues in Elixir, and encapsulation and new types was the most interesting, finally kinda got solved with this (link to article with explanation)

https://github.com/timCF/calculus#calculus

And then I used Calculus to build functional programming library

https://github.com/tkachuk-labs/wonderland#wonderland

But at some point, I just realised that it’s easier just to use Haskell where all this kind of problems are solved many years ago)

IvanR

IvanR OP

It seems the Haskel was your tool of choice. :grinning:

The Domo library is about flexibility - to give a possibility of type-safe structs in existing codebases written in Elixir.

tim2CF

tim2CF

Yeah) Once I had started with Haskell, I always wanted more - more newtypes, smart constructors, type classes, generics, dependent types, type families.. ohhh boyyy, it’s sooo huge unknown universe. Even after a year of Haskell production I think I know (understand) just around 25% of already existing stuff there, even if we exclude some overwhelming things related to Idris and Agda advanced type systems.

But regarding Erlang/Elixir I kinda gave up finally, it’s untyped languages anyway whatever you are doing with them. But it’s still cool to see people there are looking into type systems.

dimitarvp

dimitarvp

I think that library can work quite well. I don’t have any immediate remarks on it.

The main trouble is going to be social, not technical however. This effectively kind of changes Elixir (if I am understanding your Order.new! snippet correctly) and many people wouldn’t agree to adopt such a new syntax in their projects.

I also kind of gave up on the idea that the BEAM languages will have actual strong static typing for now (except maybe for Gleam that draws inspiration from Rust and thus from the ML languages as well; but it’s still figuring out its message sending interaction with the strong static typing IIRC… @lpil, am I off the mark here?).

What I do in my projects is just conservatively use as much pattern matching as possible without losing [a lot of] productivity.

Your idea is good but (a) introduces extra coding (the deftag thingies) and (b) a rather new syntax. IMO dedicated teams can make your library work for their process quite well but many others will choose not to.


If one day I have enough time and energy, I’d invest in a linter (likely as a part of credo) that could e.g. yell at you if you do this:

def do_business_stuff() do
  # ...
  Repo.insert(...)
  |> elem(1)
end

…which wrongly assumes success and blindly returns the second element of the tuple, ignoring the fact that the insert might fail and a Changeset be returned instead of the expected successfully inserted in the DB struct. (And I’ve seen Elixir code exactly like this one.)

But… that particular case could likely be covered with Dialyzer if you only add @spec to this function. Then Dialyzer could tell you that you are expecting a User to be returned but you could also get Changeset. So still not sure. As I said, just ideas that I might get to one day, life allowing (which it absolutely doesn’t for months now).


TL;DR: Looks pretty good but I am not sure it will gain traction with the requirements you put on your users.

lpil

lpil

Creator of Gleam

We’ve got statically typed messages and processes working great! There’s a few options on the table currently, so now we’re figuring out which is best and what the API should be,

IvanR

IvanR OP

Thanks for kind words.

The Domo library suggests using additional syntax for making/pattern-matching of Tags and for defining type-safe structs.

I usually address the new concept adoption issue in a team with turning up the following idea - give it a try for half an hour, and if you definitely don’t like it it’s always possible to go with something else.

Exadra37

Exadra37

I am also looking to have typed structs in Elixir to be happy enough with the language, but the boilerplate necessary always put me off, and I just go with using pattern matching and guard clauses as much as possible, just like @dimitarvp.

What I am looking for is a cleaner solution:

defmodule AuthIt.UserProfile do
  @enforce_keys [
    id: is_integer/1,
    name: is_binary/1,
  ]
  defstruct @enforce_keys
end

But I guess that this will never be possible :frowning:

IvanR

IvanR OP

One possible way to do this is with Domo in more declarative style like this:

➜  domo git:(master) cd example_app
➜  example_app git:(master) iex -S mix
Erlang/OTP 22 [erts-10.6.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Interactive Elixir (1.11.0-dev) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> defmodule AuthIt.UserProfile do
...(1)>   use Domo
...(1)>
...(1)>   typedstruct do
...(1)>     field :id, integer
...(1)>     field :name, binary
...(1)>   end
...(1)> end
{:module, AuthIt.UserProfile,
 <<70, 79, 82, 49, 0, 0, 31, 176, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 3, 2,
   0, 0, 0, 63, 25, 69, 108, 105, 120, 105, 114, 46, 65, 117, 116, 104, 73, 116,
   46, 85, 115, 101, 114, 80, 114, 111, 102, ...>>, :ok}

iex(2)> alias AuthIt.UserProfile
AuthIt.UserProfile

iex(3)> UserProfile.new!(id: 1, name: "John")
%AuthIt.UserProfile{id: 1, name: "John"}

iex(4)> UserProfile.new!(id: "John", name: 1)
** (ArgumentError) Can't construct %AuthIt.UserProfile{...} with new!([id: "John", name: 1])
    Unexpected value type for the field :id. The value "John" doesn't match the integer type.
    Unexpected value type for the field :name. The value 1 doesn't match the binary type.
    lib/domo/struct_functions_generator.ex:20: AuthIt.UserProfile.new!/1

The field types in the field macro are from the usual @type spec.

If you may want to distinguish between different ids in the app, then tags come into play. With

deftag AuthIt.UserProfile.Id, for_type: integer

you can do the following: UserProfile.new!(id: UserProfile.Id --- 1, name: "John") and get the exception/error for UserProfile.new!(id: 1, name: "John") where 1 is not a tagged integer. :blush:

Exadra37

Exadra37

I come from a background of dynamic interpreted languages, all this compilers stuff is new to me, thus sometimes I get things in the wrong way.

This being said I like your declarative way example of using the example I gave, but this:

I just don’t get it, and this tags was what made me to not like Domo in the moment I saw it. I just don’t grasp them and they have a weird syntax.

dimitarvp

dimitarvp

That’s a good example and it’s something that I could see myself doing because I go 99% the way anyway (by using guards in new / create functions). Yours is a bit cleaner. :slight_smile:

Where Next? Top

Trending in Announcing Top

woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
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
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
anuaralfetahe
Hello Published a new library - ProcessHub! ProcessHub is a library designed to manage process distribution within the Elixir cluster. ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
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
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
sergio
It’s not that it’s vocabulary is too advanced. It’s something worse. I get lost trying to follow even a paragraph written by Claude. It’...
New
AstonJ
This showed up on my feed.. anyone heard of it? Just hype? Ox Alpha is a reasoning model designed for coding, sustained ag...
New
bartblast
Hey folks, I just published a post about Hologram’s funding and where the project goes next - the short version: Curiosum as Main Spons...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews