Is it possible to declare a generic type?

Not really inheritance; an addition operation can’t inherit from a weather service. If you want to think in OOP terms this is more closely related to implementing an interface (and what else is a protocol?).

I want to be able to say, “this function takes an argument that implements protocol (interface) X”. I don’t care about its type per se, but what it does.

If we can’t represent that, I’m having trouble distinguishing protocols from classic duck-typing with nicer error messages.

That’s not possible with pattern matching and, as far as I know, with Dialyzer.

The thing is, you won’t have static typing guarantees in a dynamic language like Elixir (save some things that can be detected by Dialyzer), and at runtime, a Protocol.UndefinedError saying that “protocol X not implemented for y of type Y” will be more descriptive than a failed pattern match anyway.

If your concern is documentation, you can still do something akin to what the Enum module does: it defines its functions as taking an Enumerable.t() type, which is actually defined as term, but it makes it clear in the docs that anything implementing Enumerable is a valid argument.

One of the differences between protocols and duck-typing in OOP is that protocols allow you to define implementations in an open way without having to “own” the protocol or the data type. A protocol can be implemented by anyone, not only by the project defining the data type or by the project defining the protocol. It’s an elegant solution to the open-closed principle and the expression problem.

1 Like

Traditionally with Dialyzer if you want to say your function takes a Fooable protocol you can do:

@spec fun(Fooable.t()) :: whatever

However note that Fooable.t is basically just term. It’s great for docs, may not make any real difference in terms of concrete type checking.

1 Like

a Protocol.UndefinedError saying that “protocol X not implemented for y of type Y” will be more descriptive than a failed pattern match anyway

At runtime, sure, but not while reading the code or compiling.

it defines its functions as taking an Enumerable.t() type

This helps quite a bit, thanks for the suggestion. I think I’ll give this a try, and try to get dyalizer working on my project. Right now it just spews out a bunch of garbage from other libraries’ code.

One of the differences between protocols and duck-typing in OOP is that protocols allow you to define implementations in an open way without having to “own” the protocol or the data type. A protocol can be implemented by anyone, not only by the project defining the data type or by the project defining the protocol. It’s an elegant solution to the open-closed principle and the expression problem.

I don’t follow your reasoning on this one. When duck-typing in ruby, for example, you can definitely implement a method anywhere for another class anywhere in your code, or any library’s code, even more dynamically that elixir allows.


I guess it just feels like there should be more here. Typespecs are code, for example, and error out on the compiler if they’re not well formed. So the type-system is tightly coupled with the language. If the compiler enforces correct typespecs, it doesn’t feel like a big leap to expect it to enforce the typespecs themselves, when possible.

There is a massive leap between enforcing that a proposition is well formed and enforcing that a set of propositions are true through the whole code base. People have tried, dialyzer is about the best you can do right now given that nobody has a workable scheme for typing messages. You’re welcome to prove us all wrong though by implementing types in Elixir :wink:

1 Like

Yes, you can, but that requires reopening the class and re-defining or adding methods with all the issues it brings. The classes are open for extension, but also open for implementation changes, which means you cannot rely on a class having a particular interface unless you are aware of all the places that reopen it.

Don’t get me wrong, I love Ruby, but I also think that Elixir protocols are a superior solution to the expression problem than Ruby’s open classes.

Right. This is readily apparent if two protocols both require a function called call/2 for example. If you implement this with monkey patching or even inheritance you’re gonna run into issues because the class can only have a single #call method. However you can easily create two different protocol implementations for two different protocols that both have a call function because you always pass the data structure to the protocol, so there’s no ambiguity.

4 Likes

I do this sort of thing several places in code that introspect the module information. But it’s a pain in the ass and I only do it for absolutely critical things (for example, I got tired of mistyping config and rolling a release, so I have strong compile time checks on my config that block the release process).

There’s another place where I abuse using to check to make sure a module really really implements certain fields in it’s structs.

There is a massive leap between enforcing that a proposition is well formed and enforcing that a set of propositions are true through the whole code base.

Sure, but I never said there wasn’t a big leap on implementation difficulty. I said there wasn’t a big leap in expecting the implementation to be there.

There’s a big leap between sticking a doorknob on a piece of wood and making it open an actual door. Still, one reasonably expects a doorknob to open whatever it’s attached to.

You’re welcome to prove us all wrong though by implementing types in Elixir

Well alright, lets just not implement any improvements and consider any feedback unless they’re accompanied with a full PR by someone external to the project :wink:

This should hopefully not be the message and I’d like to give some context. Many people question the elixir community whether or why there’s no static typing capabilities or at least something more rigorous than dialyzer, while being unaware that many people did work and are working in that space. But to my understanding there has not yet been found a good way to type the message passing capabilities for processes on the beam in a manner of favorable tradeoffs. There are also a few other less critical things to consider like e.g. hot code reloading.

Therefore it’s less a matter of not accepting or implementing feedback, but rather one of the community being aware of the requests and their benefits – but not having a solution – while the question of why this is not in elixir pops up regularly. There are many people who’d like to see more static typing in elixir and piece by piece the core team does seem to add the things to the compiler that they can.

3 Likes

Thanks for the reply. This question was originally posted over a year ago, but there is no mention that something like this is being worked on or is even something the language “wants”. It would be helpful to have that linked (I found something that’s closely related but it’s inactive https://github.com/elixir-lang/elixir/issues/7541; I should have searched earlier) or just referenced, as you just did.

My intention isn’t to criticize the lack of a certain feature, but mostly to express surprise that it doesn’t exist. So much so that I wondered if I’d missed it (as, apparently, did the OP).

Just a few links in regards to type systems in elixir and on the beam in general:

3 Likes

Not to mention the latest efforts of the facebook team towards a typed Erlang https://www.facebook.com/careers/jobs/229713254864749?_rdc=1&_rdr (or something like that anyway, not sure about the exact direction of this work)

1 Like

It’s not that. It’s just that people often come here and ask that question as if nobody ever thought of that. :smiley:

I have been met with similar reactions when speaking with OCaml people – the gist was: “yeah yeah we know, we don’t have multicore support and are still working on it, it will be ready when it’s ready”.

Elixir is my favourite language amongst at least 10 I worked with plus tried as a hobby. But yep, the lack of static typing becomes a noticeable minus the longer you work with it.

There are initiatives under way already but it might be years. I’d say realistically whoever loves and likes to work with Elixir should prepare mentally for the extra effort that’s required to alleviate the lack of static typing.

1 Like

Nope, things like this are pretty much still impossible according to my understanding https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4c98b36021183b540f55bf4e8c24a55f

I’m talking about line 14 - fn print_quantity(order: OrderQuantity). It’s possible to give design a different spin, but sometimes it can mean twisting abstractions to match tool’s abilities.

The closest answer was provided by blatyi Is it possible to declare a generic type?

I’ve read on Twitter that folks at WhatsApp might be working on something up that avenue. Maybe @michalmuskala can enlight us or shed the gossip hehe :slight_smile:

1 Like