NamesAreAPain

NamesAreAPain

"Softer" defoverideable / use at bottom of script from top

Hi, I’m relatively new to Elixir, so I apologize if my question falls under the category of “you should not want this behavior”, but I’m currently stuck on a problem that I don’t have the specific vocab to easily google.

Basically, I’m trying to import another module such that the the implementations in the imported module have lower priority than those in the module that contains the import.as a bare example:

defmodule GenericImpl do
    fn test(_) do
        :A
    end
    fn call_test(x) do
        test(x)
    end
end
defmodule SpecificImpl do
    fn test(3) do
        :B
    end
end

I’m trying to find a way for SpecificImpl.call_test(3) to return :B, but for all other inputs to return ;A.

So far, I’ve tried import (which doesn’t make SpecificImpl.call_test exist at all), using “use” to insert the source of GenericImpl directly into SpecificImpl (which makes it always return :A), using defoverrideable (which makes anything SpecificImpl.call_test(3) an error), and using “use” at the bottom of SpecificImp to insert the source from GenericImpl below SpecificImpl. This last one works, but it feels dirty, and I’d like to keep cross-module dependencies at the top of the module if possible.

Is there a proper way to do this?

Most Liked

LostKobrakai

LostKobrakai

Your code looks a lot like OOP and inheritance. This is not how things are usually done in elixir. Based on your description I’d go for a behaviour based solution like this:

defmodule ImplBehaviour do
  @callback test(term) :: atom
  
  def call_test(impl, term) do
    impl.test(term)
  end
end

defmodule Generic do
  @behaviour ImplBehaviour

  @impl true
  fn test(_) do
    :A
  end
end

defmodule Specific do
  @behaviour ImplBehaviour

  @impl true
  fn test(3) do
    :B
  end
  
  fn test(term) do
    Generic.test(term)
  end
end

There’s no need for any hierarchy, meta programming or code sharing. Specific can call Generic.test/1 and it’s clear to anybody reading the code how/that this is happening.

lud

lud

In that case you could define a custom behaviour with for example a (bad named) @callback handle_special_call(message, from, state)

Then include the handle_call implementation through a macro or use MyBehaviour (which is a macro too).

And in the quoted code returned by your macro you would have something like this:

  ... previous shared handle_info clauses

  def handle_info(msg, from, state) do
    handle_special_call(msg, from, state)
  end

  def handle_special_call(_msg, _from, _state) do
    raise "Hey, you forgot to implement handle_special_call/3"
  end

  defoverridable handle_special_call: 3

Or, as others said, you just implement handle_info/3 normally, and call the parent implementation in your last clause. It’s simpler, it works well, and does not need macros.

al2o3cr

al2o3cr

You could get this effect with defoverridable and a little boilerplate:

defmodule Foo do
  defmacro __using__(_opts) do
    quote do
      def bar(arg) do
        IO.inspect(arg, label: "default bar")
      end

      defoverridable [bar: 1]
    end
  end
end

defmodule UsedFoo do
  use Foo

  def bar("wat") do
    IO.puts("CUSTOM")
  end

  # boilerplate
  def bar(x), do: super(x)
end
Qqwy

Qqwy

TypeCheck Core Team

I believe the answer might be indeed “you probably should not want this”.
Instead, we usually go for one of these two more explicit approaches:

  1. We fall back to code in another module by referencing it directly:
defmodule SimpleLogger do
  def log(term) do
    IO.inspect(term)
  end
end

defmodule IntLogger do
  def log(integer) when is_integer(integer) do
    IO.puts("We have an integer:")
    IO.inspect(integer)
  end
  def log(other) do
    SimpleLogger.log(other)
  end
end
  1. If the code you want to fall back to is injected rather than in a separate module, we fall back using defoverridable.
defmodule ExampleServer do
  use GenServer
  
  # ... other callback implementations here

  @impl true
  def terminate(reason, state) when is_integer(state) and state > 42 do
    raise "You should have terminated me sooner!"
  end
  def terminate(reason, state) do
    super(reason, state)
  end
end

Both of these make it (reasonably) clear to the reader of the code what is going on, There is no ‘hidden’ logic.


That said, it is possible to directly inject code to the bottom of a module by using a before_compile-callback (See the module-documentation of the Module module no pun intended, I swear!), regardless of where use is used.
Can you give us a more concrete explanation of what you are trying to accomplish with this pattern?

NamesAreAPain

NamesAreAPain

@Qqwy, the concrete problem I was trying to solve is that I’ve got a handful of GenServer squid that are handling RPC calls via external message brokers. The nitty gritty of the networking is identical in all of them, and they share a number of handle_call()s, but there are a handful of unique ones per application. The goal was to have the client API and those specific handles be implemented in their own modules, and then add those handles on top of the shared ones implemented in the parent module with all the networking fuss.

Where Next?

Popular in Questions Top

jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
dokuzbir
Hello, I am trying to convert my lists to string without losing brackets.For start i have 3 map. They look like these buyer = %{ id: ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New

Other popular topics Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New

We're in Beta

About us Mission Statement