Lapsang: “design to interfaces” and “dependency injection” language features

! Warning: this post hasn't been updated in over three years and so may contain out of date information.

Lapsang logo

In a previous post, I introduced the idea of Lapsang: a hypothetical language that sought to take various “best practices” of modern software engineering (or software craftsmanship, if you prefer) and support and enforce them within the language itself. This is the first of a series of articles that then seeks to show how these ideas might work in practice. In this article, I’ll focus mainly on how Lapsang might support the principles of “design to interfaces” and “dependency injection”.

First though, a note on the syntax of the code examples. I thought long and hard about my previous statement that I’d use different syntaxes for each code example. In the end, I decided this would just make things more difficult to read. So I settled on a style that I personally like. It’s influenced by a number of languages, including C# and Python. Despite using C-style languages for many years, I personally hate the use of braces in such languages, so the first decision I made was that they would go. I considered using “then” and “end” type keywords instead, but they look too old fashioned. So I settled on an idea that I used to despise, but that I’ve recently grown quite fond of: Python’s use of white space indentation to denote structure. Code – to my mind at least – is first and foremost, a description of a solution intended for other people to read. Indentation conveys code structure to the human eye in a very effective fashion, which is why we indent code in the first place. Braces are really there to help the compiler; not the reader. So I’ve used Python-style structure-through-indentation. Aside from this, I’ve mainly used a C#-style syntax (though this may not be obvious to others). Specifically I’ve tried to keep the use of symbols to a minimum, and used keywords instead where possible. It’s possible that you will hate it, as syntax and code layout are highly subjective topics. If that is the case, I simply ask that you consider the ideas before rejecting everything through a dislike of the syntax.

A central feature of Lapsang is that functions, primitives and class types cannot be referenced directly, save for self-reference (and the special case of testing). If you wish to reference a class type for example, you have to do so via an interface. To prevent this becoming an unworkable nightmare, Lapsang supports structural typing. So a class need not (in fact cannot) declare that it implements an interface. Whether a class implements an interface is determined at compile-time. This should apply to primitives too, though I have to confess I don’t really know what the “bool” or “string” interfaces would look like, or how one would implement a alternative type that successfully implemented those interfaces. So in the code examples, I gloss over this detail by just using the interfaces and by only referencing the default implementations.

To explain “by only referencing the default implementations”, I need to explain another feature of the language: dependency injection. If a section of code creates an instance of the interface, someInterface, then at compile time, the compiler needs to replace that with a concrete class type. Whilst sophisticated inversion-of-control can be applied (I’ll cover that in a later article), much of the time, the developer will want a simple solution to resolving these dependencies. The simplest way this is achieved is by interfaces declaring their default implementation. So Lapsang inverts the normal OO-style relationship between interfaces and classes. Rather than classes declaring they implement interfaces, interfaces instead declare their default implementation. I suspect many might find this a “paradigm shift too far” and will think only bad of it, but I personally think it an incredibly powerful, robust and sensible solution to what can be a horrible mess in many OO languages.

So how might all this work in practice? Well consider the following code:

Every Lapsang source file must start with a package declaration. Package/namespace declarations are nothing special, so nothing else needs to be said here on this.

Next up, the type and function interfaces used within the package must be defined. I’ve included primitives here just for completeness really. As they have no namespace, nothing is gained through explicitly listing them. So perhaps they could be implied?

The interface definition has a number of points of note, compared with other OO languages. Firstly, the properties – Owner and Permissions – are marked with “val“. The val keyword is used to denote values that can be set once and read many times. In this context, val properties have to be set via a constructor (more on that shortly) and then can only be read thereafter. Another feature of Lapsang is that type interfaces can contain declarations of factory methods, as in this case. A factory method within the interface is deemed to be implemented by a class if it supplies a method prefixed with the keyword “factory“, rather than a return type, and has the correct parameters. Finally, the interface declares that the class that follows – FileDetails – as the default implementation.

The class declaration follows the interface declaration and makes up the rest of the file. The key point of note here is the “constructor” keyword. Because all classes in Lapsang must be referenced via interfaces, the language has a choice: allow constructor signatures to be mandated by interfaces, or do away with them completely. I opted for the second option as properties should suffice as a means of constructing an object anyway. Properties marked as “val” within an interface, must be set during object construction within any implementing class. To that end, such properties are marked not as “get, set” (which would make it a mutable class, more on that in another post), but as “get, constructor“.

One last point of note is within the private static method, SetupUsingFileDetails. It contains the line of code “Permissions = file.Permissions.Core.AsString“, with “Core” deliberately highlighted in a different colour. The reason for this is composition. Lapsang does not support inheritance, so classes and interfaces cannot inherit from Object. Instead, composition is used to add functionality to aspects of the language. In this case, the Core component (I don’t yet know what form this “component” will take) is automatically added to all classes, properties etc. Core will likely contain various functionality, including the AsString property, used here.

Now let’s look at some code that uses the above interface and class definitions:

Once more, we have the package declaration, then a set of use clauses for the interfaces this package uses. Note the package is prefixed with “main“. This informs the compiler that this package defines an entry point to an application and thus can define mapping information. In this case, just “map default all“. This tells the compiler that no special dependency injection or inversion of control is needed, and that all interface references can be replaced with their default implementations, where appropriate (such as within “new” or “throw” statements.)

The Main method contains the use of a symbol that I’m currently struggling with. I’ve tried to avoid overloading the usage of symbols, so for example, “*” only means “multiply”, unlike say in C where it has multiple purposes. So I wanted “>” to always mean “greater than”, thus it’s not used for generics (braces “{}” will be used instead of angle brackets “<>” if generics are required by Lapsang). Yet using the symbol “=>” to denote a function is one I’m comfortable with and I haven’t come up with better notation. So I’ve used it here. Of course this may change in the future. The method therefore takes a set of possible file paths specified on the command line, checks whether each in turn is a file and prints details of its properties if so.

And that is pretty much it. I’ve not tried to explain every line of code as the aim was more to show Lapsang’s encapsulation of good software craftsmanship principles within the language, rather than to show off its (possibly likely to change) syntax. In the next Lapsang blog post, I’ll examine how BDD and TDD testing can be baked directly in to the language.

6 thoughts on “Lapsang: “design to interfaces” and “dependency injection” language features

  1. Can I compile this yet? 🙂

    It sounds quite interesting (my personal biases about significant whitespace aside ;). How about : instead of => ? Or are you keeping ternary operators?

    Could ‘uses’ statements be optional, only to be required in the case that the compiler can’t resolve it to one symbol? ‘uses’, much like brackets, are only really useful for the compiler (and linker) not the coder.

  2. @mnen, to be honest, I have been experimenting with Antlr, but don’t tell anyone else 😉

    I was tempted by Python’s approach to the ternary operator, as I like the idea of being able to put if statements at the end of a line, rather than the beginning. So rather than the usual:

    r = a > b ? x : y

    the syntax would be:

    r = x if a > b else y

    so “:” is a possible alternative to “=>”

    I like your idea on “uses”. I suspect I’ll adopt that for later code examples.

  3. interesting, I heard the term architecture astronaut for the first time today and fear all this deliberate abstraction to the nth degree can promote this and bring all the problems that it brings with it.

    I will keep reading though as it looks like an interesting academic exercise and who knows maybe by following this path you may come to the realisation yourself that too much abstraction can be a bad thing, a balance must be struck for it to useful.

  4. one thing to note is that many frameworks that have tried to use method/class/function naming in the past rapidly move to using some form of meta tag for example JUnit no longer (as of about JUnit 3.8 I think) needs the method to be prefixed with ‘test’ and JMX components can use an @JMX (or similar) annotation in java rather than the MBean name. I think the reason was the repeated name got in the way of the fact that the name itself should describe what the class/method/function did, but I could be wrong it may simply have been the publishers all wanted to use these new toys.

  5. in the last code snippet you declare 3 vals at different levels, what is the scoping behaviour of lapsang?

    Plus the last example reveals that (to me anyway) I think I don’t handle white space well as I didn’t realise the => will operate over everything all the way down to the next method.

  6. Richard, the scope of the vals would be the same as if the code were written like this in a Java-like language:

    There is one important difference though: args, arg, theFile, details and prop are not variables; they are write-once, read many values. So once assigned, they cannot be changed. Of course, in the case of the foreach loops, this write-once; read-many rules applies for each iteration.

Comments are closed.