jTy

jTy

Hi there! I’ve been playing with Elixir for a while and stumbled upon dependency injection. The topic comes naturally to me as having C# background where it’s pretty-well known, especially amongst professionals. Since we are dealing with callbacks and behaviors instead of abstraction and implementation while there is no such concept as an interface (which by the way, I consider if occurred, would probably be somewhere between protocol and behavior in terms of both complexity and (supposedly) ergonomics), I find it as cumbersome - an obstacle that effectively stops me from considering Elixir at production whether it is small or medium-size project., not even mentioning larger ones.
Some people pointed me to libraries that concentrate on facilitating tests (in particular test doubles - mocks) although dependency injection has nothing to do with that (admittedly we can bring them together under certain circumstances). I would love to write beautiful, cohesive, loosely-coupled programs - just not sure if that is currently possible.

I thought that maybe I can utilize what’s already existing and achieve what I want with metaprogramming - for demonstration let’s consider the following example:

  1. We start with an interface:
    i_writer.ex
defmodule IWriter do
  @callback write() :: atom()
  @callback write(args :: any()) :: atom()
  @optional_callbacks write: 0, write: 1
  defmacro __using__(_) do
    quote location: :keep do
      @behaviour IWriter
      @after_compile IWriter

      defp do_write(), do: IWriter.do_write(__MODULE__)
      defp do_write(write_args), do: IWriter.do_write(__MODULE__, write_args)
    end
  end

  def do_write(module),
    do: fn -> apply(module, :write, []) end

  def do_write(module, write_args),
    do: fn -> apply(module, :write, write_args) end

  def __after_compile__(env, _bytecode) do
    :functions
    |> env.module.__info__()
    |> Keyword.get_values(:write)
    |> case do
      [] -> raise "`write/0` _or_ `write/1` is required"
      [0] -> :ok # no args
      [1] -> :ok # with args
      [_] -> raise "Arity `0` _or_ `1` please"
      [_|_]  -> raise "Either `write/0` _or_ `write/1` please"
    end
  end
end
  1. Followed by composition:
    text_writer.ex
defmodule TextWriter do
  defstruct [:writer]

  @type wrt :: IWriter

  @opaque t :: %__MODULE__{
    writer: wrt
  }

  @spec new(writer :: IWriter) :: TextWriter.t()
  def new(writer) do
    %__MODULE__{
      writer: writer
    }
  end

  @spec write_with(writer :: IWriter) :: :ok
  def write_with(writer), do: writer.write()

  @spec write_with(writer :: IWriter, args :: any()) :: :ok
  def write_with(writer, args), do: writer.write(args)
end

And a sample implementation:
dummy_writer.ex

defmodule DummyWriter do
  use IWriter

  @type t :: __MODULE__

  def write(), do: :dummy
end

Assuming the following invocation:

    nw = TextWriter.new(DummyWriter)
    nw.writer.write()

The drawback, one amongst many, is that Dialyzer does not help us here.

Some questions:

  • Interface type - are there any plans for supporting it when proper type-system occurs in Elixir?
  • Are there any relevant approaches for compile-time dependency injection in Elixir as for now?
  • Are there any libraries out there that support all 4 dependency injection types (constructor injection, parameter injection, method injection, ambient context)?

…or maybe I’m thinking/doing something wrong? Please let me know.

First Post!

LostKobrakai

LostKobrakai

There are neither constructors nor methods in elixir, given there’s no objects. You can pass module names to functions, so parameter injection just works as far as I’m aware. No need for any library. Not sure what ambient context is meant to be.

Can you elaborate a bit more? It’s not exactly sure what your problem with behaviours is in the first place.

Most Liked

D4no0

D4no0

I have a feeling that this looks like class reinvention from languages like c# and java.

What is the actual gain of having the IWriter call the actual functions from the modules you define? if that is only to enforce the IWriter type for specs there are easier methods to do that, putting your data in a struct of type IWriter.

That aside, by using apply/3 you lose all the compile-time power as apply is executed at runtime, so dialyzer cannot solve your types at compile-time.

I find it as cumbersome - an obstacle that effectively stops me from considering Elixir at production whether it is small or medium-size project., not even mentioning larger ones.

This is quite sad to hear, as I think getting rid of classes makes the code much more readable and maintainable in the long run. Maybe instead of trying to fit elixir to the way things are done in c#, you should take a step back and learn how things are done in functional languages.

cevado

cevado

I’m too late in the discution but the simplest way of doing dependency injection in any functional language is just passing a function. there are ways to define a contract to be followed(behaviours, that some people already mentioned), but this is loosely restrict by the runtime.
Going with functions, you could have something like:

def write_with(writer) when is_function(writer, 0), do: writer.()
def write_with(writer, args) when is_function(writer, 1), do: writer.(args)
def write_with(writer, args) when is_function(writer), do: apply(wrter, args)

edit:
Usually I go with behaviours for stuff that I need a set of functions that need to be implemented and to be used together.

dimitarvp

dimitarvp

After reading your code and post, I am still not 100% sure what you are after. “Dependency injection” is just a tool, and tools are used to achieve goals. What’s your goal?

If you want to have a discrete set of implementors of a certain behaviour / protocol (the Elixir meanings of them) and then look them up / use them wherever the behaviour / protocol is needed then that can be done fairly easily and we can help you get there.

You do need to show an open mind however; insisting that Elixir must have what C# has is… not an interesting discussion. Elixir has the tools to achieve similar goals.

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New
thiagogsr
** (ArgumentError) expected :max_attempts to be a positive integer, got: {:@, [line: 10, column: 19], [{:max_attempts, [line: 10, column:...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews