rschooley

rschooley

What is the pattern for the same function implemented in different modules (strategy pattern)

This works, but I’d like to clean it up a bit (extra functions in caller):

defmodule whatever do
  defp do_thing(%{source: "foo"}) do
    IO.puts("calling foo")
  end

  defp do_thing(%{source: "bar"}) do
    IO.puts("calling bar")
  end

  def handle_info(:dont_care, state) do
    SomeContext.some_query
    |> Enum.each(fun record ->
      {:ok, yeah} = do_thing(record)
    end)
  end
end

But moving the extra private functions into their actual modules results in this error:
function do_thing/1 imported from both Bar and Foo, call is ambiguous

defmodule Foo do
  def do_thing(%{source: "foo"}) do
    IO.puts("calling foo")
  end
end

defmodule Bar do
  def do_thing(%{source: "bar"}) do
    IO.puts("calling bar")
  end
end

defmodule whatever do
  import Foo, only: [do_thing: 1]
  import Bar, only: [do_thing: 1]
  ....
end

I looked at protocols but didn’t see how they could clean this up. I’d like to just call a modules’ do_thing method without having a wrapper method for each one. Is there a way to do this? Thanks.

Most Liked

rvirding

rvirding

Creator of Erlang

To clarify things a bit. In module Whatever you are only defining one function do_thing/1 which has two clauses. While it looks 2 separate functions they are actually parts of the same function. In the modules Foo and Bar you are defining 2 different functions do_thing/1 so now you have 3 functions do_thing/1 in separate modules. What uniquely defines a function is the module its in, its name, and its arity (number of arguments). Importing does not change that, it just gives you a way of not having to prefix calling a function in another module with the module name. You still have 3 different do_thing/1 in 3 modules.

hugolnx

hugolnx

Hi, @rschooley! That’s a pretty abstract question/sample hehe

But, if your do_thing is a call that make a thing so different with each kind of record, I would guess that it structure changes a lot depending on its source. If is that the case, I think it make sense that each type of record is a struct and that exist a ThingDoer protocol, so you could easily extend this thing to be done to other record types in the future. It would be something like that:

defmodule FooRecord do
  defstruct [:content, source: "foo"]
end

defmodule BarRecord do
  defstruct [:content, source: "bar"]
end

defprotocol ThingDoer do
  def do_thing(record)
end

defimpl ThingDoer, for: FooRecord do
  def do_thing(record), do: IO.puts("calling foo")
end

defimpl ThingDoer, for: BarRecord do
  def do_thing(record), do: IO.puts("calling bar")
end

defmodule ThingDoerBoss do
  def handle_info(:get_thing_doers_to_work, state) do
    [
      %FooRecord{content: "lol"},
      %BarRecord{content: "xpto"},
    ]
    |> Enum.each(&ThingDoer.do_thing/1)
  end
end

With that solution you can easily extend the do_thing to other source structures and also have the record structures documented.

PS.: In that case, your SomeContext.some_query must return the records as structs.

PS2.: I would not recommend you to use this import overriding thing even if it worked, it seems that would be a hell of situation to navigate through modules trying to discover in which of them are the matching function hehe

BrightEyesDavid

BrightEyesDavid

I think you can simply include the module when calling the function with Foo.do_thing(), and that importing or aliasing a module is “just” a way to avoid having to use the module name for every call.

Edit: see here and here for fuller and more accurate information.

Where Next?

Popular in Questions Top

tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

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
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
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement