Programming Languages for Dependency Injection – Specialized Options

language-designprogramming-languages

Many general programming languages are flexible enough to allow you to support dependency injection. Even without library or framework support. But even if a language is Turing complete enough to solve any programming problem, a language makes choices that impact what is easy and what is hard to do in them.

Is there any language that was specifically designed to make dependency injection easy, and conversely, make creating hidden dependencies hard?

Clarification:

Due to limitations of some languages (looking at you Java) many people regard assistance with wiring and construction as part of dependency injection. Here I only intend a language designed for DI to mean that dependency's are not easily hidden in side effects. Having a convention over configuration system as well would only be added gravy.

I'm not looking for a language recommendation. It's a historical question. Has any language author ever explicitly set out to do this?

Best Answer

Yes, there is indeed. Sort of.

Newspeak has no static state and no global state. This means that the only possible way to get access to a dependency is to have it explicitly injected. Obviously, this means that the language, or in the case of Newspeak more precisely the IDE needs to make dependency injection easy, otherwise the language will be unusable.

So, the language is not designed for DI, rather the necessity for DI is a consequence of the language design.

If there is no static state and no global state, then you cannot just "reach out" in to the ether and pull something out. For example, in Java, the package structure is static state. I can just say java.lang.String and I have myself the String class. That is not possible in Newspeak. Everything you work with, has to be explicitly provided to you, otherwise you just can't get at it. So, everything is a dependency, and every dependency is explicit.

You want a string? Well, you have to first ask the stdlib object to hand you the String class. Oh, but how do you get access to the stdlib? Well, you have to first ask the platform to hand you the stdlib object. Oh, but how do you get access to the platform? Well, you have to first ask someone else to hand you the platform object. Oh, but how do you get access to that someone lese? Well, you have to first ask yet another someone else to hand you the object.

How far down the rabbit hole does this go? Where does the recursion stop? All the way, actually. It doesn't stop. Then, how can you write a program in Newspeak? Well, strictly speaking, you can't!

You need some outside entity that ties it all together. In Newspeak, that entity is the IDE. The IDE sees the whole program. It can wire the disparate pieces together. The standard pattern in Newspeak is that the central class of your application has an accessor called platform, and the Newspeak IDE injects an object into that accessor that has methods which return some of the basic necessities of programming: a String class, a Number class, an Array class, and so on.

If you want to test your application, you can inject a platform object whose File method returns a class with dummy methods. If you want to deploy your application to the cloud, you inject a platform whose File class actually is backed by Amazon S3. Cross-platform GUIs work by injecting different GUI frameworks for different OSs. Newspeak even has an experimental Newspeak-to-ECMAScript compiler and HTML-backed GUI framework that allows you to port a fully-featured GUI application from native desktop into the browser with no changes, just by injecting different GUI elements.

If you want to deploy your application, the IDE can serialize the application into an on-disk object. (Unlike its ancestor, Smalltalk, Newspeak has an out-of-image object serialization format. You don't have to take the entire image with you, precisely because all dependencies are injected: the IDE knows exactly which parts of the system your application uses and which it doesn't. So, it serializes exactly the connected subgraph of the object space that comprises your application, nothing more.)

All of this works simply by taking object-orientation to the extreme: everything is a virtual method call ("message send" in Smalltalk terminology, of which Newspeak is a descendant). Even the superclass lookup is a virtual method call! Take something like

class Foo extends Bar // using Java syntax for familiarity

or, in Newspeak:

class Foo = Bar () () : ()

In Java, this will create a name Foo in the static global namespace, and look up Bar in the static global namespace and make Bar Foo's superclass. Even in Ruby, which is much more dynamic, this will still create a static constant in the global namespace.

In Newspeak, the equivalent declaration means: create a getter method named Foo and make it return a class that looks up its superclass by calling the method named Bar. Note: this is not like Ruby, where you can put any executable Ruby code as the superclass declaration, but the code will only be executed once when the class is created and the return value of that code becomes the fixed superclass. No. The method Bar is called for every single method lookup!

This has some profound implications:

  • since a mixin is basically a class that doesn't know its superclass yet, and in Newspeak, the superclass is a dynamic virtual method call, and thus unknown, every class is automatically also a mixin. You get mixins for free.
  • since an inner class is just a method call that returns a class, you can override that method in a subclass of the outer class, so every class is virtual. You get virtual classes for free:

    class Outer {
      class Inner { /* … */ }
    }
    
    class Sub extends Outer {
      override class Inner { /* … */ }
    }
    

    Newspeak:

    class Outer = () (
      class Inner = () () : ()
    ) : ()
    
    class Sub = Outer () (
      class Inner = () () : ()
    ) : ()
    
  • since the superclass is just a method call that returns a class, you can override that method in a subclass of the outer class, inner classes defined in the superclass can have a different superclass in the subclass. You get class hierarchy inheritance for free:

    class Outer {
      class MyCoolArray extends Array { /* … */ }
    }
    
    class Sub extends Outer {
      override class Array { /* … */ }
      // Now, for instances of `Sub`, `MyCoolArray` has a different superclass 
      // than for instances of `Outer`!!!
    }
    

    Newspeak:

    class Outer = () (
      class MyCoolArray = Array () () : ()
    ) : ()
    
    class Sub = Outer () (
      class Array = () () : ()
    ) : ()
    
  • and lastly, the most important for this discussion: since (apart from the ones you defined in your class, obviously) you can only call methods in your lexically enclosing class(es) and your superclass(es), a top-level outermost class cannot call any methods at all except the ones that are explicitly injected: a top-level class doesn't have an enclosing class whose methods it could call, and it cannot have a superclass other than the default one, because the superclass declaration is a method call, and it obviously can't go to the superclass (it is the superclass) and it also can't go to the lexically enclosing class, because there isn't any. What this means is the top-level classes are completely encapsulated, they can only access what they explicitly get injected, and they only get injected what they explicitly ask for. In other words: top-level classes are modules. You get an entire module system for free. In fact, to be more precise: top-level classes are module declarations, its instances are modules. So, you get a module system with parametric module declarations and first-class modules for free, something which many, even very sophisticated, module systems cannot do.

In order to make all of this injection painless, class declarations have an unusual structure: they consist of two declarations. One is the class constructor, which is not the constructor which constructs instances of the class, but rather the constructor that constructs the environment in which the class body runs. In a Java-like syntax, it would look something like this:

class Foo(platform) extends Bar {
  Array  = platform.collections.Array
  String = platform.lang.String
  File   = platform.io.File
| // separator between class constructor and class body
  class MyArray extends Array { /* … */ }
  // Array refers to the method defined above which in turn gets it from the 
  // platform object that was passed into the class "somehow"
}

Newspeak:

class Foo using: platform = Bar (
  Array = platform collections Array
  String = platform streams String 
  File = platform files ExternalReadWriteStream
) (
  class MyArray = Array () () : ()
) : ()

Note that the way a Newspeak programmer is actually going to see the class(es) is like this:Newspeak IDE displaying multiple nested classes

I can't even begin to do it justice, though. You'll have to play around with it yourself. Gilad Bracha has given a couple of talks about various aspects of the system, including modularity. He gave a really long (2hr) talk, the first hour of which is a thorough introduction to the language, including the modularity story. Chapter 2 of The Newspeak Programming Platform covers modularity. If you skim Newspeak on Squeak – A Guide for the Perplexed (aka Newspeak-101), you get a feel for the system. Newspeak by Example is a live document (i.e. it is running inside the Newspeak-on-ECMASCript port, every line of code is editable, every result is inspectable) demonstrating the basic syntax.

But really, you have to play around with it. It is just so different from all mainstream and even most non-mainstream languages that it is hard to explain, it has to be experienced.

Related Topic