Intro to Metaprogramming in Dart

Modifying Programs at Runtime with Dart Mirrors and Reflection

Kenneth Reilly
ITNEXT

--

Introduction

Dart is a powerful language that includes features for metaprogramming, which enables the developer to extend the language using annotations and other components, and in the case of server-side and command-line software, the program can inspect and modify itself at runtime using reflection via the dart:mirrors package and some basic FP / OOP.

This can greatly improve the flexibility and modularity of an application and allow the developer to extend it at a later time with additional features without having to change much of the existing business logic, since most of the logic is decoupled through interfaces and runtime reflection.

The example used here is from a previous article, Building a Social Network: Part III, and the source repo is available here.

Overview

Metaprogramming is a powerful and flexible paradigm that enables software to treat itself or other programs as mutable data, allowing for an application to re-write itself in realtime or even produce and compile other software.

This is very different from concepts such as neural network based artificial intelligence, in which the continuous data is flexible and fuzzy but the network architecture is generally fixed and rigid (and often implemented on specialized hardware such as GPU arrays).

Software developed with metaprogramming techniques could accomplish tasks such as analysis of an unknown piece of hardware and the generation of code which may be able to utilize the hardware based on what information it can abstract from analysis, for example. Applications are limited only by imagination, but for this article, a basic example of a flexible API architecture will be used for reference.

Example Service: Content Post

Let’s take a look at an example API service:

Methods contained in this class are automatically wired up by the SDK due to this class extending APIService (which we will examine momentarily) and because annotations add serializable JSON functionality and route handling with variable parsing. This enables the developer to create very complex yet durable relationships between components and systems within a program, without having to write tons of in-line boilerplate.

One interesting note about this class is found in the user method with the usage of the call to Reflector.of<T>() (which we will also check out soon).

This creates a memory-safe new instance of any supplied type T (in this case <Post>) and copies the data from the input object to the new instance.

API Service & Base Classes

Now let’s take a look at the code for the API service base:

Here we can see the use of mirrors to apply reflection to class instances at runtime, such as the _self property of the base class as InstanceMirror which is later used to analyze metadata when new API route instances are created, allowing this class to automatically “wire-up” route handlers based on the decorators used and data contained within them.

The method used to attach the route listeners to the local route index is the _load private method, which examines the properties of the instance being created via InstanceMirror and MirrorSystem to extract the data required to bind incoming matched route requests and variables to the appropriate handler. This functional style greatly reduces code bloat and improves speed and efficiency within the underlying virtual machine due to the simple design and great freedom of choice at the level in which the interpreter is building out bytecode for the machine to process.

Reflector

Let’s now take a look at the reflector utility class:

Here we can see a few utility methods in this abstract class which are used to analyze and manipulate serializable classes and objects. The of method noted earlier is shown here along with the cast method underneath, which can produce a Serializable of type T with the same properties as the incoming object.

This allows the API framework to request arbitrary objects as needed that are type-safe and have the correct methods and properties available, without having to know at compile-time when or where these will be used.

Conclusion

Dart is an excellent language for building services, command-line tools, and other useful software, as shown by the powerful metaprogramming features available. Unfortunately, the mirrors package is not available at this point for Flutter, limiting these techniques to backend / infrastructure or local developer machine usage.

That aside, metaprogramming in Dart is still a very useful technique for building advanced software such as auto-configurable APIs or code generators and similar tools.

剣一

--

--