Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between defdelegate and def with call module function directly in Elixir

I have module that is mostly used as a namespace. Let us call it Bla. There are other submodules, that have methods with specific functions to that submodule.

However, I want to have two options:

  1. import main module Bla and use all functions from submodules directly (not to write multiple imports separately)

  2. import only specific submodule like Bla.Subbla to use functions only from this module without importing functions from other submodules

This is what I have:

defmodule Bla do
  defdelegate bla_func(text), to: Bla.Subbla
  defdelegate bla_func(text, opts), to: Bla.Subbla
end
    
defmodule Bla do
  def bla_func(text), do: Bla.Subbla.bla_func(text)
  def bla_func(text, opts), do: Bla.Subbla.bla_func(text, opts)
end

What is the right way to do that? I have two options, but have no idea, maybe, there is much more better one. Are those two options equivalent? And which one is preferable? Is there any difference in performance?

like image 322
kovpack Avatar asked Jul 04 '16 16:07

kovpack


2 Answers

A few differences I can think of:

  1. When you are using defdelegate your intent is very clear, you are just delegating the method to another method in another module. And that this is just a dumb forwarder.
  2. With defdelegate you cannot change the arguments, whether be it the order, addition or removal of arguments. So, if all you really want is to forward the method call use defdelgate as there is less scope to screw up.
  3. In some cases defdelegate is not an option as you need to add more data to the forwarding function, in which case you don't really have an option.

So, the bottomline is: if possible always use defdelegate, you will avoid a few classes of bugs using it. e.g. below:

defmodule Greet do
  def greet(name, greeting) do
    IO.puts "#{greeting} #{name}"
  end
end
    
defmodule DelegatedGreet do
  defdelegate greet(name, greeting), to: Greet
end
    
defmodule BuggyGreet do
  # scope for bugs like these
  def greet(name, greeting), do: Greet.greet(greeting, name)
end
like image 75
Khaja Minhajuddin Avatar answered Nov 18 '22 22:11

Khaja Minhajuddin


As @JustMichael told, it's good to use use in this case. It does exactly what I need.

defmodule SomeModuleWhereINeedSubbla do
  use Bla

  useful_func   # => Will print "Bla.Subbla.useful_func"
end

defmodule Bla do
  defmacro __using__(_) do
    quote do
      import Bla.Subbla
    end
  end
end

defmobule Bla.Subbla do
  def useful_func(), do: IO.puts "Bla.Subbla.useful_func"
end
like image 22
kovpack Avatar answered Nov 18 '22 22:11

kovpack