In Ruby -- mixins. In Elixir --?

I’m confused – what’s the equvivalent of mixins of Ruby in Elixir? I’m aware of macros and protocols, but what I need is inject a function that has a body, not an empty one:

defmodule MyMixin
  def hello_mixin(a) do
      hello2(a)
  end
end

And now 2 modules

defmodule M1
  def hello2(a) do
      IO.puts "something22226677777 #{a}"
  end
end

defmodule M2
  def hello2(a) do
      IO.puts "something432432432 #{a}"
  end
end

After I inject MyMixin into M1 and M2, I should be able to call M1.hello_mixin(123) and M2.hello_mixin(“fdsafds”)

How can I do this?

You can define a macro

defmodule My.Module do
  def thing do
    quote do      
      import My.Mixin
    end
  end
end

where My.Mixin has the hello_mixin() function and then use it where you need it like this

use My.Module, :thing 

you have access to mixin function this way. Phoenix does that with web module.

I’d be careful with directly translating Ruby logic to Elixir though

edit My.Module has to have the using macro

defmacro __using__(which) when is_atom(which) do
  apply(__MODULE__, which, [])
end

is it done only via macro?

This will probably not compile.

When you use Foo, then the macro Foo.__using__/1 will be called, which you did not provide.

defmodule Foo do
  defmacro __using__(_opts) do
    quote do
      def hello_mixin(a) do
        hello2(a)
      end
    end
  end
end

defmodule Bar do
  use Foo

  def hello2(a), do: "Bar: #{a}"
end

This comes closest to what you want I think. I’m not really sure though if I’d do that though… You should prefer usage of behaviours and callbacks here.

When you do use Foo in this example and miss to implement hello2/1, you will get a pretty irritating error that it were missing, pointing to a code that you do not have in your module. If you were using behvoirs and callbacks you were getting a warning, remembering to implement it to fullfil the contract.

1 Like

It’s difficult to come up with an exact analogy for mixins in Elixir because I think the core idea of a mixin at least in how it’s used in Ruby is that you’re including methods into a class. That is, the reason you use a mixin is because you want to give a set of methods access to the state of whatever object you’re mixing the module into.

In elixir however you don’t have classes or objects at all. If you want to take some data and make it accessible to a set of functions in another module you just pass that data to those functions.

Ecto is a decent example. You’ve got the Ecto.Query module with functions for building an query struct and the Ecto.Changeset module with functions for building changeset structs. Instead of “mixing in” functions from Repo to actually execute these queries or changesets, you just pass each data structure to the functions in the repo module.

6 Likes

yeah forgot to add the using part for the above to work:

defmacro __using__(which) when is_atom(which) do
  apply(__MODULE__, which, [])
end

Not sure, but it’s simple enough with macros. See other comments as for if you want to do that at all.

Basically any function (that is not a “method” cause you don’t have access to “this” anyway) can be imported or aliased as needed, or to put it other way: any module can be used as a mixin. You can save yourself some imports / aliases with the macro approach, but that’s it. Phoenix module does it with stuff common for controllers, views etc.

I use defdelegate when I want to make a function available through another module. I prefer it do the macro syntax.

With your example, it would look like

defdelegate hello2(a), to: MyMixin

You need one defdelegate per function, so you can nicely limit which functions are made available.

@benwilson512 has a very good point about using this kind of delegation with care. I only use this in places where I want a single public interface into a functionality that depends on several methods.

2 Likes

are they implemented through macroses?

What do you mean by “they”? Ecto.Query has a bunch of macros in it for building queries but that isn’t related at all to the idea of mix ins here. Ecto.Changesets involve no macros at all. The use of macros within these is sort of beside the point. What I’m saying is that instead of having

defmodule Ecto.Changeset do
  mixin Ecto.Repo
end

so that you can do model |> Ecto.Changeset.change(%{name: "foo"}) |> Ecto.Changeset.update!

Instead you just

model |> Ecto.Changeset.change(%{name: "foo"}) |> Ecto.Repo.update!

and there’s no mixin at all inside the Ecto.Changeset module.

What is the actual problem that you are trying solve with this particular approach?

Remember there are a multitude of other ways to compose and share functionality in Elixir - maybe one of them is more appropriate for the problem at hand.

defmodule MyMixin do

  def inner(arg) do
    "[[#{arg}]]"
  end

  def hello_mixin(arg, hello, out)do
    arg
    |> inner()
    |> hello.()
    |> outer()
    |> out.()
  end

  def outer(arg) do
    "((#{arg}))"
  end

end

defmodule M1 do

  def hello2(a) do
    a
    |> hello()
    |> out()
  end

  def hello_mixin(arg),
    do: MyMixin.hello_mixin arg, &hello/1, &out/1

  def hello_mixin_alt(arg) do
    arg
    |> MyMixin.inner()
    |> hello()
    |> MyMixin.outer()
    |> out()
  end

  def hello(arg),
    do: "something22226677777 #{arg}"

  def out(arg),
    do: IO.puts "#{arg} cba"

end

defmodule M2 do

  def hello2(a) do
    a
    |> hello()
    |> out()
  end

  def hello_mixin(arg),
    do: MyMixin.hello_mixin arg, &hello/1, &out/1

  def hello_mixin_alt(arg) do
    arg
    |> MyMixin.inner()
    |> hello()
    |> MyMixin.outer()
    |> out()
  end

  def hello(arg),
    do: "something432432432 #{arg}"

  def out(arg),
    do: IO.puts "#{arg} 098"

end

defmodule M3 do

  def mix_fun3(m, h, o),
    do: fn arg -> m.(arg, h, o) end

  def mix_fun4(i, h, p, o) do
    fn arg ->
      arg
      |> i.()
      |> h.()
      |> p.()
      |> o.()
    end
  end

  def mix_fun_list(funs),
    do: fn arg -> List.foldl funs, arg, &(&1.(&2)) end

  def mix_fun_comp([]),
    do: fn arg -> arg end
  def mix_fun_comp([g | r]),
    do: mix_comp r, g

  defp mix_comp([], f),
    do: f
  defp mix_comp([f|r], g),
    do: (mix_comp r, (comp f, g))

  defp comp(f, g),
    do: fn arg -> f.(g.(arg)) end

end

IO.puts (M1.hello_mixin 123)
IO.puts (M2.hello_mixin "fdsafds")
IO.puts (M1.hello_mixin_alt 123)
IO.puts (M2.hello_mixin_alt "fdsafds")
m1_3 = (M3.mix_fun3 &MyMixin.hello_mixin/3, &M1.hello/1, &M1.out/1)
m2_3 = (M3.mix_fun3 &MyMixin.hello_mixin/3, &M2.hello/1, &M2.out/1)
m1_4 = (M3.mix_fun4 &MyMixin.inner/1, &M1.hello/1, &MyMixin.outer/1, &M1.out/1)
m2_4 = (M3.mix_fun4 &MyMixin.inner/1, &M2.hello/1, &MyMixin.outer/1, &M2.out/1)
m1_fl = M3.mix_fun_list [&MyMixin.inner/1, &M1.hello/1, &MyMixin.outer/1, &M1.out/1]
m2_fl = M3.mix_fun_list [&MyMixin.inner/1, &M2.hello/1, &MyMixin.outer/1, &M2.out/1]
m1_cm = M3.mix_fun_comp [&MyMixin.inner/1, &M1.hello/1, &MyMixin.outer/1, &M1.out/1]
m2_cm = M3.mix_fun_comp [&MyMixin.inner/1, &M2.hello/1, &MyMixin.outer/1, &M2.out/1]
IO.puts m1_3.(123)
IO.puts m2_3.("fdsafds")
IO.puts m1_4.(123)
IO.puts m2_4.("fdsafds")
IO.puts m1_fl.(123)
IO.puts m2_fl.("fdsafds")
IO.puts m1_cm.(123)
IO.puts m2_cm.("fdsafds")

((something22226677777 [[123]])) cba
ok
((something432432432 [[fdsafds]])) 098
ok
((something22226677777 [[123]])) cba
ok
((something432432432 [[fdsafds]])) 098
ok
((something22226677777 [[123]])) cba
ok
((something432432432 [[fdsafds]])) 098
ok
((something22226677777 [[123]])) cba
ok
((something432432432 [[fdsafds]])) 098
ok
((something22226677777 [[123]])) cba
ok
((something432432432 [[fdsafds]])) 098
ok
((something22226677777 [[123]])) cba
ok
((something432432432 [[fdsafds]])) 098
ok
$ 
2 Likes

cha 098.

This is not a helpful reply. Please provide some information to explain what this means. You have several people all trying to help you here, please take the time to respond in a way that will let them do so, and indicates that you appreciate the time they’re taking.

5 Likes

I’d also like to see the concrete problem because for the example stated, I would’t bother about mixins at all. Even in Ruby. M1 and M2 share the same interface, so just use it. Thats duck typing in Ruby, no need for a mixin at all.

Elixir doesn’t have the notion of duck typing. But its easy dispatch calls to modules that share the same interface. And to enforce a shared interface, you can define a behaviour, as you mentioned. You can even share a default implementation, but in your stated case, it is just overkill.

IMHO, a mixin in Ruby just translates to a simple Elixir module with functions that are called explicitly, Chaining functions is so easy and readable in FP and Elixir. More often than not, it’s all you need.

4 Likes