Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: Mixing up behaviours and protocols #26

Open
Cantido opened this issue May 11, 2022 · 2 comments
Open

Suggestion: Mixing up behaviours and protocols #26

Cantido opened this issue May 11, 2022 · 2 comments
Labels
suggestion of smell Elixir-specific smells suggested by the community

Comments

@Cantido
Copy link

Cantido commented May 11, 2022

I'd like to see something about situations where you would prefer to use protocols over behaviors, or vice-versa. This is a hard decision to make, but I think I've developed a preference for protocols.

In general, I've seen behaviours misused more than protocols, where a behaviour will expect a struct of the using module's type, basically re-creating protocols but without some compiler niceness. Passing around a module name and then calling a function on it always feels awkward. I'd rather call a function on a named module so I can make sure the function exists at compile-time. I think it's harder to misuse protocols, especially because I do think they would still work in a lot of situations where behaviours would.

Here are a few things I read about this subject which can make clear that it's a hard choice:

I would welcome a discussion; like I said this is a very nuanced decision where I think some advice would be very useful.

@lucasvegi lucasvegi added the suggestion of smell Elixir-specific smells suggested by the community label May 16, 2022
@RudolfMan
Copy link

I'd rather call a function on a named module so I can make sure the function exists at compile-time.

Won't we still get a runtime error ** (Protocol.UndefinedError) protocol <ProtocolName> not implemented for ...?

Behaviours are simpler and, somehow, easier to reason about (though, I might be just not experienced enough with protocols).

However, a place where protocols fit better I think is when working on a library that we want to provide with the ability to extend the functionality (cos an implementation for a protocol from a library can be defined in the application code)

@Cantido
Copy link
Author

Cantido commented May 25, 2022

Won't we still get a runtime error ** (Protocol.UndefinedError) protocol <ProtocolName> not implemented for ...?

That will still happen, yeah. I just mean in the context of calling MyModule.myfunction/1, I can get a compile-time assurance that myfunction/1 exists on MyModule, but behaviours don't provide that, since you're calling apply(module, :myfunction []). There are guarantees that modules using behaviours have all the behaviour's functions are implemented, and they're similar to protocols there. So the caller of a protocol gets an additional check that behaviours don't.

Behaviours are simpler and, somehow, easier to reason about (though, I might be just not experienced enough with protocols).

I agree with you there. And I think that's why folks seem to lean towards behaviours. Protocols kind of twisted my brain, at first, but I've developed an appreciation for them and I have a habit of preferring them over behaviours now.

However, a place where protocols fit better I think is when working on a library that we want to provide with the ability to extend the functionality (cos an implementation for a protocol from a library can be defined in the application code)

That's where the question is, in my opinion. Both behaviours and protocols define a set of functions for the next developer to implement, so it's possible to use either. I think protocols are a little more flexible, because you can implement a protocol for someone else's struct, but you can't force someone else's module to implement a behaviour.

However there's the question of who makes the struct or where it comes from, which is a question that behaviours don't need to answer. To answer that question, a consumer of a protocol could take a configured module name, try to instantiate a struct from it, then pass it into a protocol. That gets them the same kind of dispatch that behaviours do. That also feels unpleasant to me, because structs should be data types, not just flags to dispatch a protocol on, but I hesitate to call it a smell because I can't think of any problems it would cause.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
suggestion of smell Elixir-specific smells suggested by the community
Projects
None yet
Development

No branches or pull requests

3 participants