|
This page is dedicated to the features of Nemerle and its library, which have been implemented using our meta-programming facilities.
Understanding how macros work is not necessary for using any of them. It would be useful only for knowing that those examples are just a tip of an iceberg called meta-programming and that you can easily implement even nicer things.
Languages like Eiffel or Spec# incorporate a methodology called Design by Contract to reason about programs, libraries, methods. It allows to write more secure and correct software and specify its behavior.
The language following this design must support writing explicit requirements about values on which a program operates. It contains:
We are currently on the way to add an ability to define most of those features to Nemerle. In the following subsections we present their current state, design and plans for the future.
Preconditions of a method (conditions that need to be satisfied before the method is called) can be specified using the Requires attribute.
class String { [Requires (startIdx >= 0 && startIdx <= this.Length)] public Substring (startIdx : int) : string { ... } } |
Using this attribute we can add an arbitrary assertion, keeping the body of the method clean and verbose. The compiler automatically adds runtime checks at the beginning of the method. If the condition is violated, then an AssertionException is being thrown with an appropriate message containing this expression.
Requires and other attributes can occur many times before a single method. They can be also defined directly on parameters.
ConnectTrees ([Requires (!tree1.Cyclic ())] tree1 : Graph, [Requires (!tree2.Cyclic ())] tree2 : Graph, e : Edge) : Graph { ... } |
Following the same design, we can define postconditions which the method must satisfy. This is an assertion that must be true after the execution of the method. If the method returns a value, then a symbol value is available inside the expression stating an assertion.
class LinkedList { [Ensures (IsEmpty ())] public Clear () : void { ... } [Ensures (value >= 0)] public Length () : int { ... } } |
An even more powerful feature is to give a condition, which must be true all the time during the execution of a program. We can attach invariant to any class by writing Invariant attribute before its definition.
[Invariant (position >= 0 && position <= arr.Length)] class Vector <T> { private mutable position : int = 0; private arr : array <T> = array []; public push_back (x : T) : void { ... } |
This way we can ensure that the state of our object is valid according to defined rule.
This naturally brings the problem with changing variables, which are dependent on each other in invariant. Suppose we have an assertion x == y + 5 and we cannot change any of the variables. Thus, we need a mechanism for making transactions, within which invariants are temporarily ''turned off''.
We follow the design of Spec# and add a special construct to expose the object to changes.
expose (this) { x += 3; y += 3; } |
expose takes reference to the object to be exposed and executes the given code.
In the matter of fact, invariants are not checked all the time during execution. It would be too expensive to validate them at any assignment or call to external function. We again follow design of Spec# and run assertions at the end of expose blocks and after execution of all public methods.
There are few more things which we want to implement in more or less distant future:
class V { invariant pos > 0; Add (x : int) : void requires x < 0; { ... } } |
In many programming tasks there is a need for using domain-specific languages for performing some specialized operations. The examples are regular expressions used for searching and matching parts of text, formatting strings of printf function or SQL for obtaining data from database. All those languages have their own syntax and validity rules.
Most of DSLs are used inside a general-purpose language by embedding programs written in them into strings. For example, to query a database about elements in some table, one writes an SQL statement and sends it to the database provider as a string. The common problem with this approach is verifying correctness of embedded programs - if the syntax is valid, if types of the variables used match, etc. Unfortunately, in most cases all those checks are performed at runtime, when a particular program is expected to execute, but fails with a syntax or invalid cast error.
All this happen, because the compiler of our general-purpose language treats DSL programs just as common strings. It is not surprising though - it was not designed to verify any particular domain-specific language - but it would be nice to do it before runtime. In Nemerle we can use macros to handle some of the strings in a special way - for example run a verification function against them.
This mechanism is very general and it is used in some parts of Nemerle standard library (like regular expression matching, printf-like functions). We will focus here on the design of our SQL checker.
Our library provides special functions (macros) for executing SQL queries. They have similar functionality to methods of System.Data.SqlClient.SqlCommand class, but all the strings passed to them are verified at compile-time. They are being sent to database by compiler, so the database provider is used here as a verification program. If it returns an error, the compilation is stopped with a message pointing to an invalid SQL statement.
Avoiding modification of databaseExecuteNonQuery ("INSERT INTO employee VALUES ('John', 'Boo')", conn)); |
When the compiler executes any query, it adds a transaction around it and makes a rollback after the execution to avoid modification of the database. So, an SQL statement is executed here to verify if it is correct and then it is reverted.
Safe passing values of variables to queriesMost of the queries in application are parametrized with program variables. For example, we read an employee name from a form and then search for corresponding entries in the database. In such case we want to use some variable inside the query. We can obtain it in Nemerle functions by writing the $ character followed by the name of variable.
def myparm = "John"; def count = ExecuteScalar ("SELECT COUNT FROM employee WHERE firstname = $myparm", dbcon); |
Note that passing the value of myparm is done safely using .NET database provider SqlParameter class. This prevents an often used technique of exploiting database applications using SQL code insertion (if a parameter is inserted as a string, one could set its value to some malicious SQL code)
Automatic loop and result variables creationBecause we run queries at compile-time, we can obtain additional information useful for compilation. For example, we know which columns of the table were returned by the query and what are their types. This way the compiler can automatically declare local variables and assign to them corresponding values from the result.
ExecuteReaderLoop ("SELECT * FROM employee WHERE firstname = $myparm", dbcon, { Nemerle.IO.printf ("Name: %s %s\n", firstname, lastname) }); |
The example above shows even more features. It creates a loop reading rows returned from selection one by one. For each of them, it declares variables containing values from all columns as mentioned before. Additionally, the entire query is created using a myparm variable from program scope.
For the full code of the above examples see this.
As for now see this.