fireproofsocks
This is more of a philosophical question hoping for some best-practices or practical suggestions to help write better code.
I’m working with behaviours and callbacks and it provides a sensible way to organize code into different implementations, e.g. modules that implement vendor-specific business rules.
Sometimes I want multiple implementations to refer to a shared function because it does something onerous or complicated. This makes sense from an OO perspective where you have an abstract base class and then children classes: the children classes can call methods on the parent classes, so they don’t have to re-write code.
In a functional language, however, I feel like it is a potential smell when the implementation calls shared functions. I would prefer that the implementations are NOT dependent on anything else: it would be cleaner if those modules were completely isolated.
What are ways to share/reuse functions that perform complex tasks?
- Option 1: each implementation lists a dependency
- Option 2: each implementation returns a more complex result and then the dispatching module can interpret the results and perform the more complex operations there.
- Others?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixirconf-us
- #elixir-ls
- #ai
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
OvermindDL1
The whole ‘parent/child’ thing implies an inheritance hierarchy, although you can model that in functional languages it is usually poor form to do so, rather composition is what should be done.
Hmm, this is rather open ended, I’m not sure what “implementation” is in this context or “lists a dependency” is?
Unsure on implementation again, but dispatching can be done in quite a variety of ways from witnesses to protocols to far far more. It really depends on ‘what’ is being done.
It might be good to give precise examples so they can be converted into a more functional style that would be a good comparison.
imetallica
You can have an behaviour with a “standard” implementation, much in the same line on how GenServer, Behaviour, GenStage, works.
As an example: elixir/lib/elixir/lib/gen_server.ex at main · elixir-lang/elixir · GitHub
dgmcguire
Simply calling that function to do work in the context of each module you want to use it would satisfy the need for re-use. Are you asking how you should organize shared functions? Because I’m also interested in how people choose to organize shared functions.
Personally I’m fine just having a
Utilsmodule or similar that encapsulates the often reused functions. Often reused functions to me implies a generic function and as such it seems fine to organize generic functions under a generic namespace.fireproofsocks
To provide an example, consider the following:
OrderHandlingmodule that defines a@callbacknameddo_something().VendorOnemodule implements thedo_something()callback.VendorTwomodule implements thedo_something()callback.Both
VendorOneandVendorTwomodules do vendor-specific stuff, but they both wish to call a function namedcomplex_operation(). So the question (and the reason for this post) is “where to put thecomplex_operation()function?”Option 1: rewrite the
complex_operation()function in each vendor’s module. This keepsVendorOneandVendorTwohighly modular but it violates DRY (don’t repeat yourself).Option 2: put the
complex_operation()function somewhere that bothVendorOneandVendorTwocan access it, e.g. inside aUtilsmodule. This follows DRY, but nowVendorOneandVendorTwohave a “dependency” on theUtilsmodule.Now imagine that the
VendorOneandVendorTwomodules were written and deployed as separate applications inside an umbrella app. If we want to develop these vendor apps separately (outside of the umbrella app), how can they share thecomplex_operation()code?imetallica
You may want something like this:
Then in your
AppOneapplication:Same for
VendorTwo.peerreynders
As written one could make the argument that
complex_operation()should be part ofOrderHandling(you started out with subclasses using functionality from the abstract base class) and that the callback should be respecified asdo_something(complex_operation_fun)so that the callback module can use the functionality provided by the behaviour module - in which casedo_somethingsimply becomes a higher order function.OvermindDL1
All what I’d personally do, thus:
I’d put
complex_operation/0in theOrderHandlingmodule or a specialty module depending on the transformations it should do.No point, it doesn’t do anything vendor specific thus should go into a singular module. If you really really want it callable from those modules then just
defdelegateit in.I’d probably put it on
OrderHandlingitself if it does something complex related to Order Handling, otherwise in some other more specially named module,Utilsis way too generically named so not that. No clue what’s bad about a dependency on another module as modules are just namespaced static functions (to use a C++/Java’ism).Then just have the library that supplies their behaviours also supply that function.
A dependency is only loaded once regardless of the amount of things depending on it, like object linkages in C++.
Eh I would not recommend that unless you really really want it implemented entirely internally, which is almost certainly poor form and harms maintainability.
fireproofsocks
Thanks for the response. Yes: I have tended to put things like the
complex_operation()in the “parent”-ish class (OrderHandlingin this case) for exactly the reason you articulated: because it is NOT specific to a vendor.I guess what has been bothering me is that the
VendorOneandVendorTwoimplementations have a “dependency” of sorts on theOrderHandlingmodule because each vendor implementation will specify@behaviour OrderHandling.It may sound crazy, but I’m wondering if I should NOT explicitly list that
@behaviour. In the case where I want multiple vendor integrations to exist as separate and independent apps, I don’t want them to have to know or care about theOrderHandlingmodule. I could totally write and test the vendor specific code without ever concerning myself with theOrderHandlingmodule.How crazy would it be to loosely couple the dynamic dispatching in
OrderHandlingto the vendor-specific modules? I.e. do that WITHOUT a behaviour or@callbacks. Sure, everything would fall apart if yourVendorThreemodule didn’t implement specific functions, but it could be developed and tested without ever referencing theOrderHandlingmodule. The vast majority of the time, I’d say it’s a bad idea to forgo the benefits of a contact, but if there is a pressing need to isolate implementations into their own applications, then it might make sense to skip it.This scenario reminds me of how Go implements interfaces implicitly (not explicitly). In Go, as long as your class contains the proper methods, it will be considered as a viable implementation of an interface. In this one use case, I can see a distinct benefit to that approach.
LostKobrakai
If you have a dependency – your vendor modules should be usable as callback module for OrderHandling – why are you trying so hard to hide that? What do you get by your vendor modules not knowing about OrderHandling if they need to implement it’s callback to work in the first place? If that’s not the intention then have
VendorOnebe a separate module all together and only haveVendorOneOrderHandlingbe the callback module. It could even just delegate toVendorOnewhere possible and you’d haveOrderHandlingandVendorOnefully separated.OvermindDL1
Hmm, then how else would you specify the API that they should follow? Otherwise I guess you can pray-and-hope that they follow identical interfaces so you can call them the same? :-/
But why wouldn’t you want to reference the OrderHandling module?
But again, why skip it depending on OrderHandling?