archives

Mildly Extended MixFix

This started as a response to Fortifying Macros, but then I decided it wanted to be a new post.

As some of you know, BitC's design goals include indirect support for the processes that produce robust and secure software. For example, we consider impact on code audit when evaluating language features and ideas. Because of audit in particular, I've been very resistant to adopting a macro system. I've also had real reservations about MixFix, but we ended up adopting it. In the process, two mildly clever ideas emerged that may interest people here. Neither is implemented yet, but I expect the first one to go in later today.

The first pertains to macros (thus this post). In most mixfix systems, all "holes" are equal. In ours, we decided to have two hole markers. One behaves in the usual way. The other converts its argument into a thunk. Thus, the declaration:

infix precedence _and#_

declares and to be a quasi-keyword whose first argument is eagerly evaluated and whose second argument should be wrapped in a nullary lambda. The corresponding signature for _and#_ is:

_and#_: fn (bool, fn () -> bool) -> bool

Particularly when this type of specification is combined with the automatic block-insertion behaviour of our layout implementation, a surprising number of constructs that would otherwise require macros can be fabricated. I suspect that the mechanism will ultimately be abused, but it goes a long way toward reducing the size of the core language, and it offers a weak form of "poor man's lazy evaluation".

The second idea is named syntax tables. A syntax table is simply a named set of mixfix rules. While they are not first-class values, naming them allows them to be imported across module boundaries and/or explicitly placed in effect. This means that one can do something like:

syntax SpecializedSyntax is
  ...mixfix rules here...

def f(x) = 
  let y = 5 in
    using syntax SpecializedSyntax
      arbitrary code

Unless it is specified as extending the default syntax table, a new syntax table is essentially bare (a few core constructs are implemented using internal mixfix rules; those are always present). This means that you don't get any mixfix syntax in such a table, so you are free to completely rearrange and/or redefine operators and precedence according to requirements. At the same time, major shifts in syntax are visibly signalled to the potential auditor, and confusion that might result from strange ad hoc arrangements of precedence are precluded.

It also means that mixfix doesn't ``leak'' across module boundaries without an auditor-observable syntax indicating that a change has gone into effect.

Finally, the ability to wipe the slate and rebuild the entire precedence arrangement means that the poor, struggling syntax developer isn't constrained to shoehorn their expression language into the existing precedence hierarchy.

Most of my concerns about mixfix from an audit perspective had to do with the fact that unqualified import could engender fairly substantial surprise. Naming the syntax tables and incorporating an explicit introduction syntax alleviated most of my concerns.

These may both turn out to be dumb ideas, but they were at least thought-provoking to me.