Strictly speaking on libraries
8fold UI 3.0 and type safety
We’re back with another installment of the great code migration.
Something of a misnomer because there’s not an excessive amount of code, which is why I decided to stop new feature development before it got to a point where the pain of refactoring and re-engineering was too great to overcome easily, relatively speaking.
Goals:
- Get the HTML out of our PHP (or whatever language); includes template engines.
- Divide controllers to separate what generates a view and what performs interactions with the data store.
Those are at least the two primary goals anyway.
To get the first out of the way, we refined 8fold Elements into three libraries collectively referred to as 8fold UI:
- 8fold Component (XML-like documents)
- 8fold Elements (HTML elements and attributes)
- 8fold UI Kit (full configurable components using both 8fold Elements and Component)
When we started we didn’t really know what sort of interface needed to exist. Therefore, we made two decisions that might sound counterintuitive:
- Each component would accept one argument. The argument would be a dictionary of key value pairs; thereby, allowing for infinite possible interface combinations while still not breaking should an invalid parameter be included.
- Little to no automated tests. This is slightly untrue because when 8fold Elements was first implemented we did strict TDD; mostly for the experience of doing it.
This goes against a lot of my development thinking and approach. Having said that, I do trust emerging architectures way more than planned architectures, which goes into the painters vs. carpenters concept. Having said that, I also believe in agile software development (according to Pragmatic Dave Thomas):
When faced with two or more alternatives that deliver roughly the same value, take the path that makes future change easier.
Or sustainable development (adopted by Kevlin Henney):
Sustainable development is development that meets the needs of the present without compromising the ability of future generations to meet their own needs.
So, future change easier by going with the single argument dictionary approach — check. I don’t know what future me will need, outside of HTML, which they can always get…though it may be painful — check. Agile and sustainable.
As I started gaining sight of future me, I started understanding that, without bringing in more developers willing and able to gain an understanding of the entire codebase I was heading for an unsustainable future that would not be easier to change in the future.
The visual aesthetic would be easy enough to change in the future; with well-structured HTML, all you need to know is CSS to do some amazing things without touching a line of markup. The data layers had already proven themselves easy to change in the future. This was exploring the relatively unknown territory of making the view models (HTML pages) themselves easier to change in the future; while delivering working software.
After a while of using 8fold UI Kit, the pattern for the interface emerged. This emerging architecture resulted in the 2.0 release of all the 8fold UI libraries using the make()
method. Conceptually, simple enough:
make(
$content,
array $attributes,
string $elementName,
string $extends
) {}
There are a few things of note. One, I was really excited to put the content first. With semantic HTML, the need for attributes should reduce dramatically. However, it revealed an unknown, because to make it work for all use cases I needed the first argument to allow any type. Further, I would have to manually limit the valid types: bool
, Component
, and string
. As I began converting everything to this new way of things I started ramping up the automated tests to have a better safety net against regression and, more importantly, a heightened sense of being able to do almost anything without concern.
No matter regarding that first argument being any type. And, as I was re-engineering I learned about variable-length argument lists in PHP. To be fair, I knew they existed. They are even used on some of the magic methods. But I didn’t actually understand what was going on there. Then it dawned on me, it’s a type-enabled array.
Strictly typing something basically allows the make method to say, “I accept four arguments. The first is anything you need it to be, I’ll figure it out. The second must be an array. The third and fourth must be a string. Otherwise, the application will throw a fatal exception.” Strictly typing your codebase makes it “safer” in the sense that it’s harder for me to make a method or class do something it was not intended to do.
An array in PHP is a free-for-all. The array could contain nothing but strings. But, it could also contain two strings, an instance of an object, and function reference. And, as of PHP7, I don’t believe there is a way to define a typed array, which is different than some other languages, like Swift, for example:
var
just means we want the option of changing in the future. shoppingList
is the name of the variable so we can reference it later. [String]
tells the compiler, shoppingList
is essentially of type array of strings.
We use the variable-length argument list in PHP for element attributes, because an element can have an infinite number of attributes:
attr(...$attributes)
Now, from inside the method, attributes
is an array. However, the way we use it, is a comma-delimited list:
attr('attribute1', ['attribute2'], $attributeObject)
The problem is, that it’s a free-for-all…except we can specify a type on a variable-length argument list. So, for attributes I always want it to be a string:
attr(string ...$attributes)
attr('id something', 'class something-else')
At this point the “junk-drawer” example would throw a fatal exception error. Let’s go back to make
.
make(
$content,
array $attributes,
string $elementName,
string $extends
) {}
For the most part, what I was passing as that first argument was an array of components or a string. This was becoming painful to read:
make([make('Hello', [], 'span')], ['id header'], 'h1')
First thing was to get rid of me having to call the make
method directly through the use of magic methods:
h1([span('Hello', [])], ['id head'])
My disdain and over-saturation of using arrays finally had me looking for ways to avoid using arrays even more; so, I abandoned the purely functional implementation for an instance-based approach and more the setting of attributes a method call on an instance:
h1([span('Hello')])->attr('id head')
One more array. How can I get rid of that one?
What I wanted was to call something like this:
h1(span('Hello, '), span('World!'))
So, I updated my tests to do just that. Removing the ability to pass a bool
. Unfortunately (or fortunately, depending on who you ask), there’s no way to say, “I accept this type, or this type, or this type.” That’s when I stumbled on to Elm (a purely functional JavaScript implementation), which has an HTML module quite similar to 8fold Elements. The way they solved it was to create a “text” function.
So, I thought, why not?
The type in PHP can be from the language, the standard library, a class name, or an interface implemented by a class (I don’t believe you can use trait names). So, I created an interface for the 8fold Component instance to implement and the newly minted 8fold Text instance to also implement. That means make
becomes something more like React or Elm:
make(
string $elementName,
string $attributes = [],
Compile ...$content
) {}
Which means for 8fold Component 3, you can call make
directly and do what you need to do; or, you can use the factory:
Component::h1(
Component::span(
Component::text('Hello, ')
),
Component::span(
Component::text('World!')
)
)->attr('id something')
100% type safe. Also, my test bed has grown to a pretty solid state. It was also very easy to update 8fold Component; so, it too has sit a high level of stability, which is good considering it is the lowest level.
8fold Elements was also very easy to convert, because 8fold Elements doesn’t do anything, it’s more of a storage library that can sort and check things.
8fold UI Kit also become smaller, more flexible, with improved test bed and 100% type safety as well; still not ready for the next release.
I’ve converted the home page, the about page, the policy palace, and multiple other view controllers to use the latest iterations, and it’s been pretty painless. I can’t foresee any other epiphanies when it comes to building the front-end, but will let you know.