Does Julia language need Dependency Injection?

Dependency Injection is widely used in Java, c# and some other languages as an important design pattern. How to do the same thing in Julia?

Example will be appreciated. :slight_smile:

Perhaps you could explain what you are trying to do, instead of how you do it in other languages.

Many “design patterns” in C++ and similar have no counterpart in other languages, especially with multiple dispatch, since they are not needed, or more efficient solutions are available.

9 Likes

My experience with JAVA is that dependency injection is handled by a third party libraries, not necessarily by the language itself. Not sure about C# or the other languages.

I’m not aware of any libraries that (currently) handle that, and I suspect it would be tricky. With JAVA all objects are allocated in the heap, so all references to those objects are pointers. With Julia this isn’t the case, so you would need “default” initializes for all your objects. There could also be some fun with scoping or visibly at objects in other modules.

But I don’t think those are not insurmountable problems if you would like to develop a module that does dependency injection.

I don’t think dependency injection really applies for a highly dynamic, multiple-dispatch language like Julia. How exactly something that used dependency injection in Java would translate into Julia would depend on the particular problem.

Personally I’m not a fan to dependency injection…or maybe I should say, I’ve never seen it implemented well. So far my personal experience (when debugging production issues) is that I lose track of what’s calling who and have to keep going back to the configuration files to figure out how the fricken system is configured.

That said I can see (in theory) how it would help configure a system with many objects that all need references to each other and when you want to be able to change how the system behave via a configuration file rather than code…

1 Like

You could take a look at this presentation where Stefan Karpinski explains how the problem solved by dependency injection (altering the behavior of components whose code you do not control) is nonexistent in Julia - if I understood it correctly.

2 Likes

This can to some extent be done using Cassette.jl or Irtools

Cassette is an (awesome) option, but its capabilities go pretty far beyond what’s needed for basic Java-style dependency injection. For that, you could just use some combination of parametric types, generic functions, ducktyping, and dynamic dispatch. For example, constructor injection could be done by either having a parametric, type:

struct Client{T} # or T<:Service
    service::T
end
greet(client::Client) = println("Hello ", service.name)

or an abstract-typed field (relying on dynamic dispatch):

struct Client
   service # or service::Service
end
greet(client::Client) = println("Hello ", service.name)

In these examples, you could also have name(service) instead of service.name, with name a generic function with various overloads.

The example over at https://en.wikipedia.org/wiki/Dependency_injection#Dependency_Injection_Pattern could be done simply using

greet(io::IO) = println(io, "Hello world!")
greet(stdout) # or e.g. greet(stderr)

where generic functions allow you to avoid even needing to create a Greeter type.

Though nobody would really refer to this as dependency injection here, we use this kind of stuff all time.

7 Likes

I’ve been struggling with this a lot and tried various approaches while working on Genie. It’s an important design pattern when developing a framework where the libraries need to “do stuff” within the scope of the client code. The developers (end users) work on their higher-level constructs (controllers, models, views) where they load and manipulate various dependencies of their own apps - dependencies to which Genie would not have access.

The approach that worked best for me and the current way of doing things in Genie (which I’ve also seen in other languages/frameworks) is to pass a context object which is set to the current "this". In Genie’s case, the context is the current module (ctx = @__MODULE__). At the receiving end, the library code performs Core.eval(ctx, code).

Something in the line of:

module MyModule

using Lib

function foo()
  Lib.dostuff(x, y, z, ctx = @__MODULE__)
end

end

###

module Lib

function dostuff(x, y, z; ctx::Module = @__MODULE__)
  Core.eval(ctx, thestuff(x, y, z))
end

end

Calling eval just to execute caller code sounds like a mistake. Why wouldn’t you just pass a closure, or define a generic function that callers can add methods to for their own types?

2 Likes

@stevengj I’m very curious to dive into different approaches. This sounds interesting but I’m not sure it applies to my use case (although I’d appreciate more details and/or examples).

For instance, let’s say there is some code in the controller (which is basically a function in a module). This module loads various dependencies setting up different references in its scope. Then, there’s also a view file, which is an HTML template file with embedded Julia. This view file needs access to the scope of the controller function (so that methods exposed by modules loaded in the controller are visible within this HTML template) - and needs to be processed by a library in Genie in order to output the resulting HTML response. Thus, we’re talking about sharing a variable and potentially large variable scope across multiple libraries. I don’t see how this can be represented in the form of a type that the user can define.

Re lambda, that sounds like it might work, but I’m not sure how to design it to capture user defined variable scopes.

Maybe you can clarify, please? Much appreciated, thanks!


Update 1

Sorry to waste your time, actually, in the case of the template parser it’s no longer relevant as it doesn’t apply anymore. I just looked over the codebase and in one of the refactoring sessions I got rid of all the eval-ing.

However, I do still use this technique of passing a context object for tasks where performance is not an issue. I find it very easy and convenient for one-off functions like command line utility tasks to be run at the REPL (writing boilerplate files, bootstrapping an app, etc).

I wouldn’t say that it’s non-existent, but it’s much lessened. The usual reason to need dependency injection is something like this:

  1. package A defines type T
  2. package B creates and returns objects of type T
  3. someone wants to add methods to T: they create T′ <: T with their new methods
  4. they want to use B but call T′ methods on the result
  5. this doesn’t work because B returns T objects not T′ objects
  6. dependency injection is needed to tell B to construct T′ objects instead of T objects

In Julia the process doesn’t play out like that because at step 3, you can just add methods to the original type T and you never create T′ and never get to step 6. However, a variation of the problem can happen, which is that B constructs objects of some concrete type and you want it to construct objects of some abstract type instead and be able to tell it which particular concrete subtype of that abstraction you want it to generate. This is sort of an “essential form” of dependency injection—you can’t avoid it because it’s inherent to what you want to do. So that can still happen. But it’s far less common because with multiple dispatch you’re not forced to subtype something just to add some methods to it.

12 Likes

What you describe sounds more like extension methods or “extension traits/mixins” a.k.a. type classes/ad hoc polymorphism for adding groups of methods.
Dependency injection as I know it from the JVM world means something different: it’s about swapping implementations of the same set of methods, not about adding new methods. So it’s about the strategy design pattern. In frameworks like Spring it’s usually just used to swap out the real implementations with dummy mock implementations for unit testing. It’s also used for letting the framework construct all the dependencies instead of letting the programmer do it, i.e. the framework implements the builder pattern for you.
It’s debatable if the pros of DI outweigh the cons.
I used to be pro DI in the early days of Spring (circa 2004) but in the last 10 years I’ve become pretty much anti Spring/anti DI.
So be careful what you wish for.

Sure, except that extension methods are static so you lose dynamic dispatch (and therefore the ability to use those methods in generic contexts), and mixins happen at class definition time. So neither one really addresses this issue. You can watch my talk linked above for a more thorough explanation.

There are certainly other uses of dependency injection, but the abstract factory pattern is one form of it, which is used to address the problem above.

So be careful what you wish for.

It’s unclear to me what I’m wishing for here…

1 Like

There are a couple of notions that came closely to di : inversion of control (ioc), service loader, service container.

There is too much legacy / oop orientation (pleonasm) with di. its main task is call smth w a ctor, a getter or a method. in practice ioc container eg. service container is more useful than di.

the good parts in java live there in osgi (becoming recently a bit too fat)
jboss modular service container (jboss msc), symfony servicecontainer are good fx too that can be follow.

(abstract) factory pattern came obviously after you talk to a service container

The “be careful what you wish for” was intended for the original poster :slight_smile: