Type Different: Less Code through Structural Typing or simple DI in Yeti on the JVM

Posted on March 11, 2011

6


Yeti is a simple functional statically typed language for the JVM, of which I am  quite found of .

In  this post I want to show how the flexibility of yeti’s type-system can be used to do type-safe dependency-injection (DI) with very little code.

Before I come to the dependency injection part I want to introduce Yeti’s structural type-system, which is the reason you can do DI in the way I’ll show and which is at the same time quite different from the nominal type-systems used in most other languages like Java, C# or mainly Scala.

When it walks like a duck and swims like a duck, can you see it as a duck?

The answer to this question depends on the type-system.  (Note I only speak about static type-system in this post).

In Java, C#, Scala  type-names (or class-names) matter. In these languages only instances of the class with the same name (and its explicitly by name declared subclasses) have the same type and can therefore be used interchangeable. Instances of other classes have a different type no matter whether they possibly have exactly the same fields and methods or even have the same code apart of the their name – no duck-typing here. Types are differentiated in these languages by their defined names. Therefore the type-systems of these languages are called nominal type-systems.

Contrary in yeti every data-structure and each function which is composed of the same elements has exactly the same type – no matter how it is called. Names – if there are any at all – are just cosmetic. In OO terms you could say that if two objects have the same fields and methods than they always also have the same type and can be used interchangeable regardless of their class.

Actually in such a OO-language you would not have to explicitly define classes, because if you create an object with the methods void duckSwim() and void duckWalk() than it is automatically a member of the class of objects which can be used (seen) as a duck. And for the same reason in Yeti you do not have to define a named type or class and you even can not define a named type or class (you can define aliases but these are just shortcuts and you can for Java interoperability define Java-Classes but this is a different story).

In (pure) Yeti you just create a list, a structure, a function and if it has the same element-types like another list, structure etc. than it has the same type. And if a function accesses a field duckSwim on its argument and than calls the field’s value as a function than the compiler knows this parameter belongs to the set of structures (think records) which have a duckSwim field which in turn holds a function. And the compiler than requires the caller of the function to provide such a parameter, but how this is created, where it comes from, whatever other fields it has is unimportant.

Ok so lets first look at structures a bit closer with a small example so that we than have everything together for the main – the DI part.

Structures as Record Data-Types

Yeti has built in  primitive types (boolean, number, string and unit), function types and compositional types, which are used to compose all other types.

One of the built in compositional types are structures.

Structures are record data types that contain one or more named fields. Each of the fields can have a different value and has in turn its own type.

Structure values are created using structure literals, which contain in curly-braces one or more field definitions, separated by comma. Each field can contain any value – also functions or other structures. The values of a structure are accessed using the dot plus field-name.

>pl = { distance = 2345,
            name="Jupiter",
            star= {name = "Sun", volume = 345},
            rotate speed = .... //some function here
         } 

>println pl.name
"Jupiter" is string

Like you see we did not define a class “Planet” or so. We just created the runtime-instance very much like a dynamically typed data-structure, but still the compiler can calculate a type for each field as if we had defined a class – just without a name.  If you are interested in the type the compiler infers, you can enter the above structure at the yeti-repl (all in one line) and it will print out the full-type.

And if we write a function which i.e. prints out the star of a planet, we also do not have to first define a class planet to define the function argument type:

printStarOfPlanet planet = (
     planetName = planet.name;
     starName = planet.star.name;
     println "\(planetName) belongs to \(starName)"
)

Here we also work very much like with a dynamic data-structure, but also this one is statically type-checked, because the compiler demands that the parameter planet has a name field and a star field which in turn has another name field. The compiler however does not demand that the structure has a distance or rotate field. It can have other fields but it does not have to. In fact any structure which has the name and star.name field can be printed with printStarOfPlanet.

It is obvious that this provides a lot of flexibility, because you do not have to define interfaces, sub-type-relationships etc. beforehand, but can just mix and match the demanded fields as needed when you create the structure.

So lets use that to tackle the common task of dependency-injection in a statically type checked manner, in a (possible) hierarchical fashion and scoped. And all that straight forward just in plain yeti  without any magic DI-framework.

Elegant type-safe Dependency-Injection

The example is an extended form of the example used in the excellent blog-posts: ”Pushing the envelope on OO and functional with Scala” by Debasish Ghosh and “Real-World Scala: Dependency Injection (DI)” by Jonas Bonér. This blog-posts are as their name say about DI in Scala, which is of course another very powerful modern JVM language – with a much more powerful type-system and feature-set than yeti but also more complicated (especially to read and understand).

To make things a bit more realistic and to show some features we use a few components:

  1. A requestHandler function which handles a HttpServletRequest, loads the current user from the session and than delegates to the renderPage function
  2. the renderPage function that renders the html and uses the productService to render the product-of-the-month recommended to the current user
  3. the userService – which for the example just delegates to the DAO
  4. the productService – which also just delegates to the DAO
  5. and a DAO for both the userService and the productService; one dummyDAO and one productionDAO
  6. a HttpServlet which plumbs all together

I will not implement the full code just what is needed to show the DI concept, but still this sounds like a lot of code and interfaces just for a blog-post. Well just scroll down to the end of the post to see all the code together.

First we implement the userService and a dummyDAO. We put all the code in the yetidi.yeti file, which is the module which contains all the code.

module yetidi;

import javax.servlet.http.HttpServletRequest;

di = {

    userService ctxt = {
        loadUser userId = ctxt.userDAO.readUser userId,
        storeUser user = ctxt.userDAO.createUser user.name user.userId,
        removeUser userId = ctxt.userDAO.deleteUser userId,
    },

    dummyDAO = {
        readUser name = {userId = name, name = name},
        createUser name uid = {userId = uid, name = name},
        deleteUser name = true,
        readProduct name = {itemNumber = name, name = name}
    },
};

di

Like in a typical yeti module we create a main structure which contains all the fields/functions of the module and return that at the end. (We store the main-structure in a variable, because we will need it for testing- which we do for brevity just in the module and we also want to put the HttpServlet class in the same module and this has to access this structure).

First you will note that we do not define any interfaces or classes, we code right away the implementation of the production and dummy components.

The first function userService takes a ctxt (context) argument and creates a structure which loads, stores, removes Users – the userService. The userService in turn looks up its DAO in the passed in context.

You might wonder why a context is passed in and not the userDAO directly. The reason is that the userService could itself create another component with dependencies. Now instead of declaring this additional dependencies the userService just passes ctxt further and the compiler will itself take care (infer) that the context has all the dependencies. We will see that later in the requestHandler function.

As the userService delegates all calls to the userDAO we also create a dummyDAO with the needed functions (see again no interface here).

Before going further the dummyDAO is used to test the userService. At the following two lines to the very end of the module (until the ——) and load the module in the repl.

uS = di.userService {userDAO = di.dummyDAO};
uS.loadUser "foo";
--------
//result is:
{name="foo", userId="foo"} is {name is string, userId is string}

First the userService is created, using our dummyDAO and than we load the user “foo”. As expected this returns a structure with name=”foo” and userId=”foo”. Note that the compiler has inferred the full-type {name is string, userId is string}.

Everything is fine so we go on with the productService:

productService ctxt = {
    loadProduct name = ctxt.productDAO.readProduct name,
    productOfTheDay userId = (
        u = ctxt.userService.loadUser userId;
        //choose some product for the user and load it
        loadProduct "itemFor-\(u.name)"
    ),
},

dummyDAO = {
    readUser name = {userId = name, name = name},
    createUser name uid = {userId = uid, name = name},
    deleteUser name = true,
    readProduct name = {itemNumber = name, name = name}
},

The productService is similar implemented as the userService: It takes a context from which it looks up its productDAO and just delegates to it.

Additional it provides a productOfTheDay which is used by the view to show an advertisement for a given customer. The productOfTheDay function in turn uses the userService. So the productService has two dependencies the productDAO and the userService.

Finally we just extend our dummyDAO with the readProduct function – in production we could separate the DAOs.

Lets test it again:

uS = di.userService {userDAO = di.dummyDAO};
pS = di.productService {productDAO = di.dummyDAO, userService = uS};

pS.productOfTheDay "Joe";
-------------------
{itemNumber="itemFor-Joe", name="itemFor-Joe"} is {itemNumber is string, name is string}

Again we first create the userService , than we create the productService injecting the productDAO and the userService we have just created. And finally we get as expected an item with the right type. Note that we have not once defined a type or interface, no parameter type, no item class, no return-type nothing.

But could we now test the productDAO without using the real userService? Sure:

pS = di.productService {productDAO = di.dummyDAO,
                        userService = {
                            loadUser ui = {userId = "foo\(ui)",
                                               name = "foo\(ui)"}}};
pS.productOfTheDay "Joe";
------
{itemNumber="itemFor-fooJoe", name="itemFor-fooJoe"} is {itemNumber is string, name is string}

This time we inject through the context a mock userService which we create on the fly. And additional we only implement the function which is actually needed by the productService (loadUser) and not all the functions which are by chance on a fictive interface of userService because the “interface” of userService from the point of the productService is just the function loadUser. That’s what the compiler says, because all this is still completely statically type-checked. I.e. If we would misspell laodUser the compiler would immediately complain.

The final two pieces of our puzzle are the requestHandler and the renderPage. Both are just functions not structs, because the only do one thing (which is nice because yeti is after all a functional language):

    requestHandler ctxt req is 'a -> ~HttpServletRequest -> 'b = (
        userId = if defined? req#getSession()#getAttribute("user-id") then
                    string req#getSession()#getAttribute("user-id")
                 else
                    "anonymous"
                 fi;
        user = ctxt.userService.loadUser userId;

        scopedCtxt = ctxt with {user = user};
        content = renderPage scopedCtxt;
        {status=200, headers = ["Content-Type":"text/html;charset=UTF-8"], content}
    ),

As always the requestHandler takes a ctxt which contains the dependencies additional it takes a HttpServletRequest argument.

Because the req argument is a Java-Class we have to use the first time a type-annotations (is …). For a description of what type-annotations are, when and how to use them  please read the yeti introduction. But in short they are not only needed when Java-Classes are involved, they are also very handy when you are lost in the types, because than you can annotate functions, variables etc. with types and the compiler checks whether the types you expect match the types the compiler infers.

After the requestHandler has loaded the user of the current session it creates a scopedCtxt.

The scopedCtxt is a copy of the original ctxt enriched with the user. The with keyword means: take the structure on the left, copy it and add to the copy all the fields from the right structure (or replace them if they have the same name).

And this is of course great to create a scoped context for the current request only, which contains exactly the same dependencies as the global context but additional the current user  – without bothering the receiver of the scopedCtxt (renderPage) with what is in which scopes.

Finally we call the renderPage function. This function while it is also a dependency is not injected, because also in practice you of do not inject each dependency especially if you do not have to be super-flexible and for readability prefer a more direct style, or because it is a deep hierarchy where injection would be just to much work and in our example we do not inject the renderPage because we want to demonstrate the flexibility of the context.

    renderPage ctxt = (
       user = ctxt.user;
       productOfDay = ctxt.productService.productOfTheDay user.userId;
       """<html><head></head><body>
           <div id="product">Hi \(user.name), this is nice: \(productOfDay.name)</div>
          </body><html>
       """;
    ),

As you can see we take the user from the scoped ctxt which the requestHandler provides, but the interesting part here is the dependency on the productService.

But the interesting thing here is that the renderPage takes (like always) the productService from the context as a dependency. However without knowledge of the requestHandler or declaration of this dependency in the requestHandler itself, although the requestHandler has alone chosen to use the renderPage function and injects everything needed into this function. But while the dependency of the renderPage is completely transparent to the requestHandler the one who uses the requestHandler must provide this dependency – enforced by the compiler.  And if you would decide that renderPage needs yet another dependency you just take it from the context without modifying the requestHandler at all and the compiler will again enforce that the original ctxt contains this new dependency.

The dependency is passed totally transparently  through the requestHandler and also the other way round: the static type-requirement for the dependency is passed totally transparent to the caller of the requestHandler through the requestHandler

I leave the unit-testing to you and just want to finally plug everything together in a HttpServlet (of course coded in yeti):

productionDAO = {
        readUser name = {userId = name, name = name},
        createUser name uid = {userId = uid, name = name},
        deleteUser name = true,
        readProduct name = {itemNumber = name, name = name}
},

class YetidiServlet extends HttpServlet

    handler = (

        uS = di.userService {userDAO = di.productionDAO};
        pS = di.productService {productDAO = di.productionDAO, userService = uS};
        di.requestHandler {userService = uS, productService = pS, user = {userId = "an", name = "an"}}
    ),

    void doService (HttpServletRequest req, HttpServletResponse res)
         c= handler req;
         res#setStatus(c.status);
         forHash c.headers do key value: res#setHeader(key,value); done;
         wr = res#getWriter();
         wr#print(c.content);
         wr#flush(),

end;

At the end of the blog you can find the full code. I just want to add that – at least I – would have a very hard time to do statically type-checked DI in a possibly hierarchical style with scoped contexts just with the means of the language in as little code with any of the other statically typed JVM languages.

But are there disadvantages in structural typing and if so are they worth it?

Unfortunately yes there are disadvantages:

  1. Structural typing is (arguably) more flexible than nominal typing, but this comes at the price that certain mistakes are not caught by the compiler: I.e. if you have a customer structure which you pass mistakenly to a function which expects a supplier which has the same fields (i.e. name, balance) than yeti wont catch that. If you want you can easily prevent this with using Variant Tags (some thing for a later post), but you must do that explicitly while nominative type-systems enforce this always
  2. The yeti-type-system adds more limitations to otherwise legal code than more complex type-systems which require type-annotations. I.e. you can not create structures with polymorphic functions through function application. So in our example the productService and userService could not have polymorphic functions. However for “business-service” structures (instead of libraries) you do not that often need polymorphic functions. And if you need them there are ways to work around the limitation (standalone polymorphic functions which take the contexts via a parameter or you could always trick the compiler via casting).
  3. Another disadvantage (at least in yeti) is that because the types have no names and are only described by there structure, you often get very long hard to read type-descriptions in compile error-messages.
  4. Subtyping  is also better supported in nominal type-systems, but also here Variant (or algebraic data-types) are often the better choice and are supported by yeti. In this context it is also notable, that yeti also supports recursive Structures and Variants without explicit declarations

But IMO all these disadvantages do not justify the more code and lost flexibility you get with the nominal type-systems.

And this seems to be confirmed by the fact that structural typing gets more popular in recent time. Googles Go language supports it, also scala (while it is not the “default”) and the more and more popular functional languages Haskell and ML (the origin of yeti) as well.

The best thing is to try it yourself. Just take a final look at the DI example code and give yeti a try. It is easy to learn, because it is simple and still has very powerful possibilities.

In my experience it is a real pleasure to work with yeti, unmatched by any language on the JVM.

 

Appendix: The complete sample code

.

module yetidi;

import javax.servlet.http:HttpServletRequest, HttpServletResponse, HttpServlet;

di = {

    userService ctxt = {
        loadUser userId = ctxt.userDAO.readUser userId,
        storeUser user = ctxt.userDAO.createUser user.name user.userId,
        removeUser userId = ctxt.userDAO.deleteUser userId,
    },

    productService ctxt = {
        loadProduct name = ctxt.productDAO.readProduct name,
        productOfTheDay userId = (
            u = ctxt.userService.loadUser userId;
            //choose some product for the user and load it
            loadProduct "itemFor-\(u.name)"
        ),
    },

    dummyDAO = {
        readUser name = {userId = name, name = name},
        createUser name uid = {userId = uid, name = name},
        deleteUser name = true,
        readProduct name = {itemNumber = name, name = name}
    },

    productionDAO = {
        readUser name = {userId = name, name = name},
        createUser name uid = {userId = uid, name = name},
        deleteUser name = true,
        readProduct name = {itemNumber = name, name = name}
    },

    requestHandler ctxt req is 'a -> ~HttpServletRequest -> 'b = (
        userId = if defined? req#getSession()#getAttribute("user-id") then
                    string req#getSession()#getAttribute("user-id")
                 else
                    "anonymous"
                 fi;
        user = ctxt.userService.loadUser userId;

        scopedCtxt = ctxt with {user = user};
        content = renderPage scopedCtxt;
        {status=200, headers = ["Content-Type":"text/html;charset=UTF-8"], content}
    ),

    renderPage ctxt = (
       user = ctxt.user;
       productOfDay = ctxt.productService.productOfTheDay user.userId;
       """<html><head></head><body>
           <div id="product">Hi \(user.name), this is nice: \(productOfDay.name)</div>
          </body><html>
       """;
    ),

};

class YetidiServlet extends HttpServlet

    handler = (

        uS = di.userService {userDAO = di.productionDAO};
        pS = di.productService {productDAO = di.productionDAO, userService = uS};
        di.requestHandler {userService = uS, productService = pS, user = {userId = "an", name = "an"}}
    ),

    void doService (HttpServletRequest req, HttpServletResponse res)
         c= handler req;
         res#setStatus(c.status);
         forHash c.headers do key value: res#setHeader(key,value); done;
         wr = res#getWriter();
         wr#print(c.content);
         wr#flush(),
end;

di;

Advertisements
Posted in: Yeti