Kawaii is a small library aimed at exploring microtyping in Java - below I set out what microtyping is, the problems with using it in Java, approaches for minimising these problems, and possible ways forward.
What is microtyping?
"Microtyping", also known as "tiny typing", refers to a pattern in which code avoids using primitive classes such as String or Integer and instead uses "microtypes" which wrap a primitive value to confer a specific type identity to it, and is seen as the cure to primitive obsession. For example, instead of:
by using microtyping we can write:
Microtyping allows us to extend the type system to confer the advantages of typing on primitive values:
Microtyping allows us to extend the type system to confer the advantages of typing on primitive values:
- Making the code self-documenting.
- Allowing the compiler to offer better compile time security and other tools to offer better inspection/refactoring support.
- Allowing specific microtypes to define validity rules - eg an Integer microtype that allows only positive values.
Why is this a problem in Java?
I have seen microtyping used from time to time, and it's been discussed before, but my experience microtyping is not common in Java. This is because, unlike some other languages (Ada, D, Go etc), microtyping in Java is unwieldy:
- Primitive types are final (for security amongst other reasons) and cannot be subtyped.
- But type system offers no alternative to subtyping, ie no alias or typedef functionality.
And as far as I can see, neither new language functionality in Java 8 nor proposals for Java 9 improve on this, although I'd love to be proved wrong.
The only option this leaves is wrapping the primitive inside another class - but this can potentially involve a lot of boilerplate code when creating, and clumsy integration when using.
The only option this leaves is wrapping the primitive inside another class - but this can potentially involve a lot of boilerplate code when creating, and clumsy integration when using.
Boilerplate
Although Project Lombok reduces the boilerplate when creating value classes, and Java 8 add some interesting possibilities for value object creation, values objects are not microtypes:
- A value object will typically wrap one or more primitive values or other value objects - but it won't act as it if it is a single primitive value.
- A microtype will always wrap a single primitive value, and to some extent will be interoperable with it.
In other words, a microtype can be seen as special case of a value object, but with additional library integration.
Given this, Kawaii uses an abstract base wrapper class (MicroType.java) which is extended to create a hierarchy of microtypes - for example JSONString.java which extends MicroType<String> (JSONString is abstract, since we would expect other specific concrete JSON classes to extend this, for example PersonJSONString.java).
The major boilerplate issue here is that due to Java inheritance rules, each subtype needs a constructor. Lombok could be used, but given that most IDE's will auto-generate the class and constructor, and once created it's not going to need to be looked at again, I don't feel this is actually a practical problem.
Integration
Since Kawaii wraps rather than extends primitives, it's not possible to use the microtypes as direct replacements for primitives when calling Java or 3rd party libraries. For example, given a microtype "Name" extending MicroType<String>:
- Can't call "doSomething(String)" directly - have to unbox by calling "name.value()"
- Can't infer method returns of String to Name - have to box by calling "new Name(value)"
This has the potential to pollute code, but by pushing the boxing/unboxing down as far as possible, and using adapters for external libraries, the issue can largely be avoided.
For instance, JacksonJSONTest.java demonstrates how:
- ClassPathResources.java can be used to allow reading a file from the classpath into a MicroType<String>.
- KawaiiObjectMapper.java can be used to read and write from a Java POJO that contains microtypes to a JSONString microtype, using the Jackson JSON processor.
- A custom Jackson serializer (MicroTypeSerializer.java) ensures that JSON produced when Jackson serialises a POJO containing microtypes is the same as if it had contained primitives.
resulting in code which does not use the String primitive directly at all:
Note that in cases where a method returns a new microtype instance, it is necessary to pass in the class, so for instance instead of "writeValueAsString(Object)" we have "writeValueAs(Class<? extends JSONString>, Object)". New microtype instances are reflectively created in these cases using the static helper MicroTypes.java.
What next?
Firstly, some questions I do not intend to answer in this post:
In my view, the major hurdle required to move forward is adopting a standard approach. Short of adding language-level support in Java, the next best would be to have a MicroType class in the JDK libraries, in the same way Optional was added in Java 8. This would allow 3rd-party libraries to add support, as is already happening for Optional. I could of course continue to expand Kawaii by adding adapters for other libraries (JAX-RS, JDBC, JAXB etc), but this will always be inferior to the libraries directly supporting a standardised MicroType.
Comments? Is microtyping an idea whose time has come in Java? Or it it's practical utility to small for it to ever catch on?
- Is all this extra effort worth it? This is a subjective question and depends for instance on the size of the team/codebase, complexity, style etc - but for the purposes of this discussion I will assume there are at least some circumstances where it is.
- Why not just use language X which already supports microtypes? For some projects this may be an option, but choice of languages depends on many factors. I will assume there are at least some existing Java projects which will benefit, or new projects which will use Java for other reasons.
In my view, the major hurdle required to move forward is adopting a standard approach. Short of adding language-level support in Java, the next best would be to have a MicroType class in the JDK libraries, in the same way Optional was added in Java 8. This would allow 3rd-party libraries to add support, as is already happening for Optional. I could of course continue to expand Kawaii by adding adapters for other libraries (JAX-RS, JDBC, JAXB etc), but this will always be inferior to the libraries directly supporting a standardised MicroType.
Comments? Is microtyping an idea whose time has come in Java? Or it it's practical utility to small for it to ever catch on?
I'm not sure why you call it Microtyping.
ReplyDeleteTo me it's simply proper domain modeling.
For many of the types you used in your example a more complex internal representation than just a sequence of characters is indicated.
NakedObjects aka Apache Isis followed that methodology years ago and Java 9 might introduce value types (although, again, things like EmailAdresses are potentially proper objects with identity).
I'm calling it "microtyping" since that seems to be the prevalent term in the Java community. Microtyping applies to cases where otherwise just a simple primitive would have been used, so it does potentially form part of proper domain modelling.
DeleteAs I mentioned in the post, I think micro types can be seen as a special case of value types, but they are not the same thing.
See also further comments on Reddit
ReplyDeleteWhile we're doing micro types, how about let's have some in-between types. For example. Instead of the incredibly general purpose String, let's have some new types like: HtmlSafeString, SqlSafeString, UnsafeString. Obviously incoming strings from the browser could be Unsafe strings. A function could turn an UnsafeString into an SqlSafeString or an HtmlSafeString. You could store SqlSafeStrings into the database. Upon retrieval convert them into HtmlSafeStrings before including them in an outgoing web page.
ReplyDeleteThen a micro type like First Name could come in various flavors: FirstNameHtmlSafe, LastNameHtmlSafe, etc.
Of course, now we're talking about an explosion of types.
If web frameworks and database drivers would use the new safe / unsafe types, then you've just used type checking to eliminate entire classes of error. Sort of reminds you of using type checking to prevent accidental assignment between integers, strings and booleans.