Compile Time Languages
Let’s say I want to call a stored procedure in a database. The stored procedure is called GetBooksByAuthor
, and takes one parameter, “author”, which is a string.
It would be nice to call it like this:
database.GetBooksByAuthor(someAuthor);
But how do we do that? Currently, there are only a few ways I can think of:
- A code generator could connect to the database, obtain all the stored procedures, and populate a class called “database” with the appropriate functions
- The same thing, but we could interact with the generator to only get a few stored procedures. Of course this means that we have to manually intervene every time we need a new procedure.
- We use a dynamic language, which is able to take the function name as the string “GetBooksByAuthor” at run-time, and dynamically populate the call to the database
The code generators are generally a bad idea. For one thing, they generate code (and normally pretty bad code), which just clutters up the real code. They also require a separate step. Before we code against a new stored procedure (or a changed one) we need to (re)generate the code that proxies for the stored procedure. This can’t be done automatically, since it happens before we code (otherwise the auto-completion and wonderful things like that wouldn’t work).
Also, in the case where we don’t do a brute-force fetch-all approach, we also need to specify which procedures we want to bind to. This list of procedures is probably stored with the code generator and not with the code that actually calls the function. This is terrible for code locality. Often the generated code will be part of the data-access-layer of the program, while the code that actually uses it will be somewhere else.
On the other hand, if we use a dynamic language, we lose quite a few things:
- Static type checking
- Performance
- Auto-completion and other IDE assistence
But what if we could get the best of both worlds? What if we could use “dynamic” functions, but execute them at compile time? Consider this example, based on a hypothetical language which is related to C#:
meta class Database : IClass { string connectionString1; string connectionString2; // Constructor Database(string connectionString1, string connectionString2) { this.connectionString1 = connectionString1; this.connectionString2 = connectionString2; } IEnumerable<IFunction> FindFunctions(string name) { // .. Connect to database, find stored proc // using connectionString1 } object Call(IFunction function, object[] arguments) { // Call the given stored procedure using connectionString2 } } interface IFunction { string Name { get; } IEnumerable<IParameter> Params { get; } Type ResultType { get; } string Description { get; } } interface IParameter { string Name { get; } Type ParamType { get; } string Description { get; } } static void main(string[] args) { static var connStr1 = "Server=myServerAddress;Database=myDataBase;Trusted_Connection=True;"; var connStr2 = args[0]; var database = new Database(connStr1, connStr2); var books = database.GetBooksByAuthor("Smith"); }
In the above example, it would pretty much just “work” in C# if you took out the words “meta” and “static var” – although the database.GetBooksByAuthor
would instead look like database.Call(...)
. The word “meta” would simply tell the compiler to translate calls from the form database.GetBooksByAuthor(...)
into calls of the form database.call("GetBooksByAuthor", ...)
. This is nothing interesting, and has nothing to do with compile time vs runtime – it should work the same either way (in this hypothetical language).
Why are there two connection strings? Well, you might not notice it, but normally there are two connection strings. You use one connection string in your development/build for your code generator or IDE, and often a different connection string (provided dynamically as a configuration) for your production environment. The above program just makes it more obvious – and it also puts them in the same place.
This example would work perfectly fine at run-time. But the interesting thing is what happens if you use partial-evaluation to specialize the Database
class at compile time. In particular, I think the runtime program could be simplified by the compiler down to the following:
class Database { string connectionString2; // Constructor Database(string connectionString2) { this.connectionString2 = connectionString2; } IEnumerable<Book> GetBooksByAuthor(string author) { // Call the given stored procedure using connectionString2 } } static void main(string[] args) { var connStr2 = args[0]; var database = new Database(connStr2); var books = database.GetBooksByAuthor("Smith"); }
Moreover, since a lot of it can be evaluated at compile time, the IDE could also provide auto-completion and other information. For example, when you type “database.”, the IDE can provide a list of stored procedures (by executing user code).
Taking it further
This could be used for more than database access. It could be used to provide linking to files of completely different languages, even if they exist in the same program. You wouldn’t need compiler extensions to be able to link to C++, all you would need is a library which knows how to call a C++ ABI.