I am close to using a behaviour as a pure interface and I feel dirty...

Brett Hemes brhemes@REDACTED
Wed Aug 25 17:33:23 CEST 2021


Thank you Stanislav for taking the time to iterate.  This is nice.

> Though I am on the side of Alan Kay's definition of the OOP - objects communicating with messages.

> Anyway, how you "slice" the world and how you make your abstractions - it is influenced by the "classic OOP".

> Of course it is very tempting to call behaviour an interface, because it looks like an interface.
> But it is not because the interface in Erlang are the functions, not a separate entity.

Indeed, but all of the teaching texts are very quick to advocate wrapping your implementations with API calls that delegate to the underlying implementation.  This in my mind adds a layer of abstraction on top of pure message passing... abstractions that play to the weaknesses of "poisoned" practitioners ;)

That said, your answer does help; thank you again very much!

I am still not all the way there yet, but at a much better place than a few days ago.  My current impression at this point is that the topic is nuanced and perhaps I should be thinking more along the lines of "quit THINKING of behaviours in terms of interfaces" or more generally, "quit THINIKING in terms of interfaces at all"?

I found it interesting in your example you call `create/1` in main_app at runtime using `DBmod` where my misguided thinking would have tried to use `callbacks_db` instead (interface thinking).  ...this might be my disconnect.

> It is an instrument to remind you to implement functions.

As I type this I think most of my issues stem from the fear of changing something in the future and not having compile-time checks to help me find all the references (and I saw the behaviour as a nice compile-time check (crutch?) that looked attractive).  This is probably a bigger and different issue all together that I need to learn more about within the Erlang ecosystem.  I am assuming it is some combination of supervision, Dialyzer, and really good tests but I am sure I will learn this quite quickly after deploying some real stuff in the wild...  I just would rather learn as much as I can before that time comes.


Thanks,
Brett


From: Stanislav Ledenev <s.ledenev@REDACTED>
Sent: Wednesday, August 25, 2021 4:04 AM
To: Brett Hemes <brhemes@REDACTED>
Cc: erlang-questions@REDACTED
Subject: [EXTERNAL] Re: Re: I am close to using a behaviour as a pure interface and I feel dirty...

OK, I'll try to give you my opinion on this topic. If I understand your question correctly.
It may look rude but please believe me it is not my intention to be rude.

Your examples for me are the evidence of the "classic OOP" poisoning.
Remark:
I call it "classic" based on how the vast majority of the developers perceive the idea of objects and
interaction between them.
Though I am on the side of Alan Kay's definition of the OOP - objects communicating with messages.

Anyway, how you "slice" the world and how you make your abstractions - it is influenced by the "classic OOP".
Erlang is the functional language (more or less) so abstractions are made the other way:
1. Basic units are Module and Function;
2. Dynamic function calls - Module:Function(Args). So you can dynamically (in runtime) change the module;
3. Behaviour is just a hint for a compiler to check a Module for a presence of the required functions.

Based on these 3 points - when you write -behaviour(....) in a module you are just saying that
this module must have a bunch of functions implemented to be successfully compiled. It is not for
supporting your own abstractions. It is an instrument to remind you to implement functions.

What you call "general" functionality in Erlang is simply a library implemented as module(s).

Of course it is very tempting to call behaviour an interface, because it looks like an interface.
But it is not because the interface in Erlang are the functions, not a separate entity.

Example of using behaviour from my own experience.
As in almost any application I must have something where I store the data.
And requirements are very vague. So I don't know if it would be Mnesia, PostgreSQL, file, space station or a toaster.
So I've made the behaviour which defines functions for my business logic associated with storage.
To make it simple it is like the CRUD operations on my entities.
This behaviour is implemented as an independent rebar3 library, so other parties (sometimes remote) can include it as a dependency.
But their implementations are included in the final application and what implementation will be used
is defined in the configuration file.
(Pseudo) code is like this:

callbacks_db.erl:
    -module(callbacks_db).
    -callback create(Entity::my_entity_t()) -> ok | {error, Result :: term()}.

postgresql.erl:
    -module(postgresql).
    -behaviour(callbacks_db).
    create(Entity) -> ....

toaster_able_to_storage_data.erl:
    -module(toaster_able_to_storage_data).
    -behaviour(callbacks_db).
    create(Entity) -> ....

main_app.config:
     {main_app, [
         {db, #{mod => postgresql}} % If you are using a toaster use "toaster_able_to_storage_data"
     ]}

main_app.erl:
    some_function(DBMod) ->
        Entity = create_entity(...),
        DBmod:create(Entity).

That is all about behaviour.

ср, 25 авг. 2021 г. в 00:46, Brett Hemes <brhemes@REDACTED<mailto:brhemes@REDACTED>>:
> Erlang's behaviour is NOT an interface!
> If you are looking for some analogy from other languages the closest one is abstract classes in C#.

This isn’t very helpful...  it is the exact same response I find in the forums with no reasoning behind it.  I don’t need analogies either; I understand what behaviours are “supposed” to be from the documentation and comments (perhaps this wasn’t made clear by my post).  Where I fall short is “why” are behaviours limited to such and why aren’t more people asking the same questions I am stuck on (regarding polymorphism)?  My logic was: yes, this has been asked and discussed some in the past with no real resolution that I could find... therefore, users must be content/accepting of the tools provided.  I am not so naive to think I am the first to need/want such, so there must be a disconnect.

I posted my example to motivate my questioning hoping for some insight and/or comfort.  As of now, I have proceeded with storing “meta refs” to my child servers that are module/reference tuples (along with some dangerous and future-maintenance-issue-causing assumptions regarding their “interface”)... and it’s works... it just smells, and I am always eager to learn and find the right/better/best way.

Aside: a colleague came across this repo (https://github.com/eldarko/epolymorph) while digging and the readme seems to capture my use case almost exactly...

Brett
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20210825/212c17b0/attachment.htm>


More information about the erlang-questions mailing list