Dependency Injection alternatives for web development

I think the approach used here (see section 4.4.2) is also worth looking at: using opium, you can just put all the dependencies in the opium environment.
It’s a bit rougher, but will also be less work to extend and refactor if you don’t have a well planed path.

thanks @sapristi I checked this and its nice for simple use cases, but ideally in a more complex use case the data access layer should not know anything about the server it is running on

By this, perhaps you mean the way everything gets functorized in a way that repeats functor-arguments, I guess? This is due to having to show the type-checker that the same (e.g.) Log module is injected everywhere, rather than diferent ones in different places. More generally, a problem with Java’s approach to DI is that there’s no way to ensure type-relationships between different things that are injected; with functorized modules, there is.

1 Like

@Chet_Murthy what do you mean with with that?
That there is no way to ensure that all interfaces have related implementations or?

I don’t know if you ca do it in Java, but for example if yo had two dependencies, a serializer and a deserializer, that they both operated on the same value type.

@orbits, If I understood you correctly, you can have a generic type defined either at the class level or function/method level (and they can be mixed and matched):

TS Playground

Yes, the issue I’m describing is that when you have multiple dependencies injected,it’s not possible to specify&enforce identities among them in the language.

DI is a poor man’s functors.

I think there is a good reason for that : if I understand correctly, DI is classes parameterize by classes, but classes in Java are poor man’s modules. Since, functors are modules parameterize by modules, DI is a poor man’s functors. :wink:

1 Like

Oh, for sure. for sure. I didn’t mean to imply that there was some other reason for it. Just noting that this is the case.

ETA: Maybe relevant to note that it’s quite possible to, within the context of Java, produce something like functors that are type-safe in the sense I described. Just … it requires implementing something like CLR assemblies. Ah well, maybe Java will get there sometime in the '20s.

I’m not sure I understand

Here is a Sketch link that has 2 classes with the same identity enforced (in OCaml, but the same thing is possible in all other languages)

DI is classes parameterize by classes, but classes in Java are poor man’s modules.

yes, but what does this mean - what does it mean that modules are better than classes - I’m trying to figure out in what way?
What are examples of problems solved by modules, but not classes?
Or is there a higher runtime cost of classes?

I am genuinely trying to understand

Concretely, if you have a big system, you can multiple Java classes that each satisfy the same interface. With DI, you can inject each of those Java classes into code that expects that interface. Imagine you have multiple implementations of a Logger, L1 to a file, L2 to a DB, L3 over the network. Suppose that we have a large module M that is injected with L3. And the UI infrastructure that uses M is also injected with a Logger, maybe L1. There’s no way for the UI infrastructure to know or ensure that M.Logger is the same as its own Logger.

This might matter for performance reasons.

But there’s lots more pitfalls down this road that are worse.

I don’t know if @Chet_Murthy is correct in his claimed limitation of DI in Java.

Your question here can be interpreted in a few ways: do you mean the distinction between classes and modules in Ocaml, or comparing Ocaml modules to, say, classes in Java?

My understanding is that there is not a big distinction between classes and modules, semantically, in Ocaml. Specifically, after first class modules were introduced. Prior to first class modules there were more distinctions. Perhaps someone with more knowledge of the types could speak to this. One important distinction, however, is that functors let you specify polymorphism at compile time over modules, which I think might suit DI a bit better. With classes, the polymorphism happens through late binding because anytime you have some instance of that class it could actually be something that sometimes it, so the method calls have to have some sort of run-time lookup (this might be elided in some places). With functors, you say, at compile time, what all that binding is, so it isn’t late binding.

I think this is useful because much of the polymorphism people are used to in class-based languages is probably unneeded. A lot of places where we really just want a different instance of a class for testing, so when we’re in production it will always be the same type ,and in testing it will always be the same type, but production and testing have different types, forces us to have semantics where every usage of a value could be any type that subclasses it.

I’m not sure how well I explained that. A possible analogy is a functor could be kind of like parameterizing how you build a car. You pass into the functor the engine, color, etc, and you get out a built car. Whereas with classes, it would be more like every time you use the car you first have to figure out what kind of are it is because the universe hasn’t guaranteed the car has changed between uses. The analogy could probably use some work.

I think, conceptually, many people feel more comfortable with modules, even if first class modules are very similar to classes.

You don’t even need the object system to do this, polymorphic records are sufficient. Just use this type:

'a serializer = {
  serialize : 'a -> string ;
  deserialize : string -> 'a

By the way modules, records and object share a same property : they are product type, i.e. they bind names to values, but with module you can also bind names to types.

For instance if I have a signature like this:

module type S = sig
  type t
  val make : string -> int -> t
  val name : t -> string
  val age : t -> int

I can implement a module with this signature like this:

module M : S = struct
  type t = {
    name : string ;
    age : int

  let name p =
  let age = p.age

With OOP I’ll have private attribute and accessor method, i.e. from a certain point of view an object that mimic this module is something similar to this:

type 'a s_as_an_object = (module M : S with type t='a) * 'a

that is we pack together a value of type 'a (the private attribute) and the methods that operate on it (the module). But with the limitation that with an object, we cannot project out the type of the attribute, because in an object dictionary there is no name for types.

And this is why @orbitz said that first class module and object are similar. With them we can define a function f with this type:

f : (module M : S with type t='a) -> 'a -> string

and if we remove curryfication we have:

f : (module M : S with type t='a) * 'a -> string

(* that is *)

f : 'a s_as_an_object -> string
1 Like

I meant DI in Java (using non-generic classes, so no parametric polymorphism) vs. functors in OCaml.

Sure, if you start using parametric polymorphism in Java, you can get closer to what OCaml offers. But at base, if I have functor F : A -> B and I apply it twice to M1 and M2 (and a type in A shows up in B) then F(M1) and F(M2 will be different modules, and the types that show up in their module-types will be different. If I have two instances of a Java class C that implements interface B, and I inject two different instances of classes that implement interface A, then the two instances of B remain of the same type, and if I invoke a method on one that returns a value of a type used in interface A, it’ll be type-compatible with any method that takes a value of that type.

Java (without using generics or high-level classloader magic) has no way of expressing these distinctions.

That’s all I was saying.

Java has had generics for a really long time, so it’s a pretty arbitrary limitation to put on Java and really not right to say Java doesn’t support this.

Sure, Java progresses. And my understanding of DI is from back when it was “invented”. I’d be happy to see some evidence that DI today can somehow express the same equations and inequations among types, that OCaml modules achieve. But given that the core of DI is to use runtime configuration to set up the equivalent of functor applications, I’m skeptical that this is actually doable.

I don’t think this has been true for awhile. Certainly there are libraries that do it at runtime, but I believe dagger2 has been doing this at compile time for awhile. Your Java knowledge may be so outdated that it may no longer be very valuable.

TL;DR If Java is finally catching up to OCaml modules circa (checks notes) 2000, or SML modules (checks notes again) circa 1990, I’m happy for them! Better late than never!

Are you saying that dagger2 can produce the sort of generative types that you get with ML modularity? That would be interesting, and in the context of Java classes and classloaders, a significant advance. Here’s a trivial example I cooked-up just for this email. B1.apply doesn’t accept values of type A2.t for its second argument.

module type A = sig
  type t

module type B = sig
  module M : A
  type t2
  val apply : t2 -> M.t -> M.t

module F(M : A) : B = struct
  module M = M
  type t2 = M.t -> M.t
  let apply f x = f x

module A1 = struct
  type t = int

module A2 = struct
  type t = string

module B1 = F(A1)
module B2 = F(A2)
1 Like

I mean that I can not get the dependency information that I express with functors at runtime in order to call start() on the modules in the right order. I guess it might be possible to generate some dependency graph from functors with a ppx? Right now I have to say “X depends on Y and Y” twice, once with functors and once with as values.

But I’d pick that anytime over ClassNotFoundExceptions :slight_smile:

I’m unclear on what you mean. Do you have example code?

I know that in Java, because “classes run their initializers when first referenced” it is completely possible for entire subsystems to only be initialized when they’re first accessed. So in a web-app accessing 3 DBs, since the Sybase and DB2 DBs are used by almost all servlets, those conn-pools get initialized early. Since very few, rarely-used, servlets access Oracle, the Oracle conn-pool gets initialized … a week later.

Now, if DI is expected to initialize the conn-pool as they’re stitched-together, that would correspond to putting initialization into module-static So it wouldn’t be in start() functions, but rather in the modules as let _ = blablabla items.

Maybe example code would help?