Code generation is a common solution to the interoperability challenge. It is employed not only for data access, but also for dealing with web services and in RPC technologies in general (ASN.1, protobuf, to name only a few).
Despite having traditionally good support from integrated development environments, this approach leaves much to be desired. Generated code is often unintelligible, is hard to be customized by hand (since the customizations might be lost after regeneration) and, what's also very important, needs to be fostered by the programmer. Finally, it's virtually impossible to implement any sufficiently complex code generator without turning it into a spaghetti of textual templates and control flow that expresses generation logic (the proliferation of various template engines somewhat supports the stated fact).
Compile-time metaprogramming techniques come to the rescue. By leveraging these techniques, the developer can programmatically generate required classes into the compiler instead of having to provide them in the source code form. One of the popular approaches that involves compile-time codegen is represented by F# type providers (take a read of an excellent blog post "F# type providers - as if by magic..." that outlines the underlying principles behind type providers):
[<TypeProvider>] type VectorProvider() = interface ITypeProvider with // ... interface IProvidedNamespace with // ... // Cheating for simplicity type Vector() = inherit obj() [<TypeProvider>] type VectorProvider() = interface IProvidedNamespace with member this.ResolveTypeName(typeName) = // ... member this.NamespaceName with get() = // ... member this.GetNestedNamespaces() = // ... member this.GetTypes() = // ...
We propose an approach of even greater power that leverages the foundations of a macro system to generate the code during the compile-time. Instead of being a dedicated ad-hoc syntactic construct, macro types are smoothly incorporated into Scala.
On the one hand, macro types are full-fledged macros, i.e. they can accept parameters in AST form, they can generate resulting ASTs, which provides
necessary flexibility to address code generation tasks.
On the other hand, macro types are interchangeable with regular types: they can be inherited, they can be mixed in, they can participate in instantiation of generic types
and, finally, they can be used as first-class modules (
objects in Scala). The latter is a topic of a separate installation
(see the "Generation of boilerplate" case study).
type MySqlDb(connString: String) = macro ... type MyDb = Base with MySqlDb("Server=127.0.0.1;Database=Foo;") val products = new MyDb().products products.filter(p => p.name.startsWith("foo")).toList
All in all, macro types solve common problems with code generation by providing flexible extension points into the generation process and keeping the generator itself readable and maintainable. This introduces robust solutions to the problems that inevitably require writing boilerplate in modern mainstream languages.