I’m running Debian testing, with gnome-flashback. At present, it has
installed gnome-flashback 3.20.2-1 alongside gnome-settings-daemon
3.21.90-2.
Symptoms
By default, there are some problems:
The “Displays” section of the config tool says only “Could not get
screen information”. This prevents GUI access to a lot of
functionality, including arrangement of multiple monitors.
The brightness adjustment keys do not work.
The root of the problem is that the DBus interface
org.gnome.Mutter.DisplayConfig changed between GNOME 3.20 and GNOME
3.21; the GetResources method produces an additional result in 3.21
that is not present in 3.20’s version of the same
interface.1
If you see messages similar to the following from stderr of
gnome-settings-daemon at startup, you are suffering from this problem
too:
(gnome-settings-daemon:7149): power-plugin-WARNING **: Could not create GnomeRRScreen: Method 'GetResources' returned type '(ua(uxiiiiiuaua{sv})a(uxiausauaua{sv})a(uxuud)ii)', but expected '(ua(uxiiiiiuaua{sv})a(uxiausauaua{sv})a(uxuudu)ii)'
(gnome-settings-daemon:7149): wacom-plugin-WARNING **: Failed to create GnomeRRScreen: Method 'GetResources' returned type '(ua(uxiiiiiuaua{sv})a(uxiausauaua{sv})a(uxuud)ii)', but expected '(ua(uxiiiiiuaua{sv})a(uxiausauaua{sv})a(uxuudu)ii)'
(gnome-settings-daemon:7149): color-plugin-WARNING **: failed to get screens: Method 'GetResources' returned type '(ua(uxiiiiiuaua{sv})a(uxiausauaua{sv})a(uxuud)ii)', but expected '(ua(uxiiiiiuaua{sv})a(uxiausauaua{sv})a(uxuudu)ii)'
(gnome-settings-daemon:7149): common-plugin-WARNING **: Failed to construct RR screen: Method 'GetResources' returned type '(ua(uxiiiiiuaua{sv})a(uxiausauaua{sv})a(uxuud)ii)', but expected '(ua(uxiiiiiuaua{sv})a(uxiausauaua{sv})a(uxuudu)ii)'
Fixing the problem
To fix the problem, I patched and rebuilt gnome-flashback with these
commands:
After these steps, you should have a new gnome-flashback binary,
gnome-flashback-3.20.2/gnome-flashback/gnome-flashback. You can now
move the existing one out of the way and install it:
Now restart both gnome-flashback and gnome-settings-daemon. (You
should arrange for gnome-flashback to be started after
gnome-settings-daemon.)
You should no longer have the stderr output complaining about the RR
screen DBus signatures, and both the “Displays” section of the config
tool and the brightness keys should work.
It’s a shame that there’s no apparent
protocol versioning in the GNOME system, or at least none that
applies here. If DBus had a serialization language a little more
like protobufs,
it’d be possible to smoothly add fields in a backwards-compatible
way. ↩
Like the
previous post, this post is written as a literate Racket source
file, and is licensed CC-BY
4.0. You can download it
and run it yourself.
The Communicating Fragment: Actors in Redex
Here, we build on the functional fragment language that we
developed in Part I, adding communication primitives to
it.
Here’s a little program in the extended language. It creates an
initial configuration holding a “boot” actor, which spawns two
more actors, which each send themselves a message:
Nondeterminism enters the mix in a big way at this point, even for
such a small, simple program as this. The traces for this example
show a lot of branching, and the more complex a program is, the
more possible interleavings it will have:
Preliminaries
As before, we need the Racket #lang header, and we need to load
Redex itself. Since we’re also making use of the model we
developed last time, we load that too. It is contained in a file
called redex-iswim.rkt in the same directory as
this file.
We define our Actor language by extending the syntax of the
plain ISWIM-like language we developed last time.
We add four new kinds of primitive expressions, one new primitive
type, and a bunch of structure related to representing entire
groups of actors syntactically.
A syntax extension is declared by invoking
define-extended-language with the name of the extended language,
ISWIM+Actors, and the name of the language to be extended,
ISWIM, which was imported from redex-iswim.rkt above.
(define-extended-languageISWIM+ActorsISWIM
Expressions, Values, Process IDs and Contexts
Starting with the new expressions, we let actors send messages to peers
with (send ...), receive messages sent to them by their peers with
(receive), discover their own identities with (self), and spawn new
actors with (spawn ...).
(expr....(sendexprexpr);; sends the second arg to the first arg, a PID(receive);; blocks, waiting for the next message(self);; evaluates to the PID of the calling actor(spawnexpr...));; spawns a new actor which performs the exprs.
Values in our extended language include all the values from
before, namely numbers, strings, booleans and so on, plus the
addition of Process IDs, PIDs.
(value....pid)
We choose to represent PIDs using symbols—variable names—since
that way, we can use Redex’s built-in name-freshening machinery to
guarantee that a newly-spawned actor receives a genuinely fresh
PID that is shared by no other actor in the group.
(pidvariable-not-otherwise-mentioned)
Our evaluation contexts also need to be updated for the new
syntax. Here, only send expressions involve evaluating subterms
before being processed, so we have only a small change to make to
the context nonterminal from ISWIM.
(context....(sendcontextexpr)(sendpidcontext))
Writing it this way forces evaluation order in a send to be
left-to-right, consistent with all the other syntactic forms in
the language.
Configurations
The main difference between the reduction relation we’ll build and
the one we built last time is that this one will operate on whole
configurations of actors rather than on individual ISWIM
expressions.
We introduce a new syntactic category, configuration, which
represents a pair of a queue of messages yet to be delivered, and
a process table.
(configuration((queued-message...)(actor...)))
A queued message is a pair of an ISWIM value and the PID that
it is addressed to. (The => is punctuation which makes reading
terms in interactive use easier.) I’ve chosen the convention of
adding new entries at the right-hand end of the queue, and
removing entries from the left-hand end.
(queued-message(value=>pid))
An entry in the process table, an actor, is a triple of the
actor’s PID, its mailbox, and its current program state in the
form of an expression in the extended ISWIM+Actors syntax. The
mailbox is a queue of messages, keeping the same convention
described above.
(actor(pid:mailboxexpr))(mailbox(value...)))
We didn’t introduce any new binding constructs, so we don’t have
to mention #:binding-forms like we did for ISWIM.
Operational Semantics
Reduction Rules
While Redex does offer
extend-reduction-relation,
it doesn’t work well for us here because we are going to be
interpreting ISWIM’s notions of reduction with a new kind of
context: with configurations carrying contexts instead of
plain contexts alone.
Our first rule, send,
queues the message to be sent and the pid naming its target in the
configuration’s buffer, and replaces the (send ...) expression with
#t.
Notice the “actor_0 ...” and “actor_1 ...” subpatterns. These allow
Redex to pick any actor that matches the rest of the pattern,
nondeterministically. As we will see, such subpatterns also occur in
each of the remaining rules.
After a send rule has placed a message addressed to some pid in the
configuration’s buffer, the deliver rule then takes it, finds the
actor named pid in the process table, and places the message in its
mailbox. Everything else is left unchanged.
But what if the addressee no longer exists? This can’t happen with
this version of the language (see
exercises), but could happen if we were
to alter the language in one of any number of simple, harmless
ways. The failed-delivery rule simply drops buffered messages in
the case that the addressee is not in the process table.
The receive rule selects the next ready message from an actor’s
mailbox. The way the rule is written, it is only applicable if the
mailbox is nonempty. This automatically makes actors block if they
invoke (receive) when they have empty mailboxes: their term is
simply stuck until some matching message comes along.
The self rule replaces occurrences of (self) that are (a) in
some evaluation context that is in turn (b) in some actor that is
in turn (c) in our global configuration. We know syntactically
which actor it is that’s invoking (self) so it’s easy to choose
which pid to replace (self) with.
Finally, we copy the beta, delta, begin-one, begin-many,
if-true, if-false and unfold rules from ISWIM
verbatim.3 Because they’re exactly the same
as before, I’ve omitted them from the write-up; they are, however,
still in the program source code.
;; (ISWIM notions of reduction omitted from presentation)
Following these ISWIM notions of reduction, we define what the ==>shortcut
arrow means. Here it’s expressed in terms of our configurations, not
just in terms of context the way that plain ISWIM assigned meaning to
it. This lets the plain ISWIM notions of reduction function in any
reducible actor in the process table.
The delta, subst-all and let metafunctions are identical to
those presented last time, so I’ll omit them in this write-up.
(They are in the source code, however.)
;; (Metafunctions `delta`, `subst-all` and `let` omitted)
However, for our new system we do need one new metafunction,
boot-actor, which takes a sequence of exprs and places it in a
starting configuration for the reduction relation to operate on.
As before, we put a few simple tests in a test submodule.
(module+test
This checks that ISWIM reductions can happen in a running actor.
The expected result (() ((boot : () 124))) is a configuration
with an empty buffer and with one actor, whose PID is boot, with
an empty mailbox and an expression that is a value 124.
Notice the large number of intermediate terms before the final
answer is computed! There are many possible interleavings of
execution of this little program, leading to a lot of
nondeterminism.
The corresponding test takes a correspondingly long time to execute.
What can we do about this slowness? Part III will look
into some possibilities.
Conclusion
We have extended our model of an ISWIM-like language with
Actor-style facilities including spawn, process IDs, and message
sending and receiving. We kept the ISWIM notions of reduction, but
placed them into a new kind of context: configurations. We also
added new rules that act on configurations directly to perform the
Actor-related effects that programs written in our language can
now request. In Part III, we will explore ways of reducing
inessential nondeterminism in order to gain efficiency.
Exercises
(Easy.) Alter the receive rule to nondeterministically choose
any of the queued messages in the mailbox. This removes the
axiom of ordered delivery from the model.
(Easy.) Add a rule called
message-loss that nondeterministically leads to the loss of a sent
message. From which queue did you choose to remove the message? What
other possibilities exist? Can you make an analogy between the
different queues in a configuration and the buffers and queues in
your operating system kernel and in your Ethernet cards, your LAN
switches, and your routers?
Hint: Because making this change introduces nondeterminism, you
will want to add additional possible outcomes to the test-->>
forms in the test submodule.
(Easy.) Develop an
argument for why programs written in our Actor language will never
invoke the failed-delivery delivery rule. What assumptions do
you need to make for your argument to hold?
(Medium.) Add support for a new (quit) expression that
terminates the invoking actor and removes it from the process
table.
(Hard.) Add a garbage-collection rule which removes
“finished” actors from the configuration’s process table. When is
an actor “finished”? Is there an appropriate syntactic criterion
you can use to decide? Is your approach sound? Is it complete?
(Medium.) Add support for Erlang-style
monitors
(see also
here)
and exit-notification-messages.
References and Footnotes
C. Hewitt, P. Bishop, and R. Steiger, “A universal
modular ACTOR formalism for artificial intelligence,” in Proc.
International Joint Conference on Artificial Intelligence,
1973, pp. 235–245.
Available online.↩
J. De Koster, T. Van Cutsem, and W. De Meuter, “43 Years of Actors: a
Taxonomy of Actor Models and Their Key Properties,” in Proc. AGERE, Amsterdam, The
Netherlands, Oct. 2016, pp. 31–40. doi: 10.1145/3001886.3001890.
Available online.↩
I tried to figure out a way of reusing the
actual definitions from ISWIM, but couldn’t do it. Does anyone
have any tips for me? This is the problem I was alluding to at
the beginning of the “Operational Semantics” section. ↩
This is the first part of a series of three articles on modelling
actor-like systems,12 similar to
Erlang and to my own work on
Syndicate, using PLT
Redex, “a domain-specific
language designed for specifying and debugging operational
semantics.”
Write down a grammar and the reduction rules, and PLT Redex
allows you to interactively explore terms and to use randomized
test generation to attempt to falsify properties of your
semantics.
In this post, I’ll build a model of a simple “ISWIM”-style,
lambda-calculus based functional programming language. In
subsequent posts, I’ll extend this little model to build it into
an Erlang-style actor system.
And here’s a screenshot of the Redex trace visualizer showing
reductions of the program:
Preliminaries
ISWIM is a programming
language gedankenexperiment invented by Peter Landin in his
famous paper, “The Next 700 Programming Languages”.3 It
is essentially a Scheme-like language; a fairly minimal extension
of the lambda calculus.
Our model will be an ISWIM-like language supporting S-expressions,
numbers, strings, and booleans.
First of all, we need the Racket #lang header:
#langracket
Next, we load Redex.
(requireredex)
Syntax
We begin by declaring the (S-expression-based) syntax we will use
for programs written in our language.
(define-languageISWIM
Expressions, Values and Variables
Our first nonterminal is expr, syntax for expressions in our
language. An expression can be any of the following options:
(exprx;; a variable referencevalue;; a value (see below)(consexprexpr);; the construction of a pair(exprexpr...);; a function call(primexpr...);; a primitive operation call(beginexpr...);; a sequence of expressions(ifexprexprexpr);; a conditional(recxexpr));; a recursive expression
Notice the lack of imperative constructions. When we get to Part
II, we’ll add some imperative commands, like “send message”.
Despite this lack, we still let programs written in this language
use begin, because it is more ISWIM-ish than specifically
Actors-ish, and it will be useful later.4
Values, value, in our language can be any of the following:
(value(lambda(x...)expr...);; a literal functionnumber;; a literal numberstring;; a literal stringboolean;; a literal booleannil;; the special value `nil`(consvaluevalue));; a constructed pair of values
We’re playing an interesting trick here, letting the cons
constructor serve as both an expression and as a value.
Once both arguments to cons have been reduced to values,
the whole piece of syntax represents the final pair.
We let the syntactic nonterminal x stand for variables in our
language:
(xvariable-not-otherwise-mentioned)
Evaluation Contexts and Primitive Operators
Our model uses the idea of evaluation contexts invented by
Felleisen et al. in 1986.567
An evaluation context is a term with a hole in it. The hole is,
roughly, the position in the term where some reduction can happen:
where a redex is waiting to be reduced.
This definition of evaluation contexts forces evaluation of
function arguments to happen in left-to-right order.
We define a small set of primitive operators for manipulating data
in our language. Other operators can be added by extending the
definition of the prim nonterminal and by adding clauses to the
metafunction delta below.
(prim+-=carcdrpair?null?)
Binding structure of the language
I first learned Redex by taking a course based on “Semantics
Engineering with PLT Redex”.8 An important part of
programming with Redex at the time was implementing substitution
functions correctly, respecting the binding structure of the
language. This was a difficult, error-prone and annoying part of
using the system.
Since then, Paul Stansifer has completed his dissertation
work,9 and as a result, Redex programmers can specify
a language’s binding structure directly as part of the syntax,
meaning that substitution and alpha-equivalence functions can be
automatically produced by the system.
We no longer have to write substitution functions by hand!
Here, to make use of Paul’s work, we specify the binding structure
for lambda and rec forms: the formal parameters to a function
are bound in the function body, and the recursively-bound
identifier is bound in the contained expression, respectively.
The documentation for this neat feature is available
here.
Operational Semantics
Reduction Rules
Our reduction relation will combine a notion of reduction,
capturing the essence of each reduction rule, with use of the
evaluation contexts we defined earlier. Roughly speaking, the
way we use evaluation contexts makes a notion of reduction in
every potentially-reducible position in a complex term, even
though the notion of reduction is specified in terms of a
particular redex and reduct isolated from their context.
(defineISWIM-red(reduction-relationISWIM
The first notion of reduction is the beta rule, for function
calls. We will see the definition of the subst-all metafunction
below.
Primitive operators are interpreted by delegating to the delta
metafunction, also defined below.
(==>(primvalue...)(deltaprim(value...))delta)
When only one expression remains in a begin expression, we
should replace the begin with the expression.
(==>(beginexpr)exprbegin-one)
If the first expression in a begin has been completely reduced
(to a value), then discard the value, and move on to reducing
the remaining expressions in the begin.
If the test position in an if has reduced to #t (Scheme
notation for “true”), then replace the whole if with just the
“then”-part.
(==>(if#texpr_0expr_1)expr_0if-true)
Likewise with the “else” part, in case the test position reduced
to #f (“false”).
(==>(if#fexpr_0expr_1)expr_1if-false)
Recursive values, usually functions, are handled by unfolding
them: replacing uses of the recursively-bound variable in the body
with a copy of the whole recursive value itself.
(==>(recxexpr)(substituteexprx(recxexpr))unfold)
This is the first use we’ve seen of the generic
substitute metafunction that Redex provides for us
when we specify binding structure in the way we did above.
Finally, we bring together the notions of reduction with the
contexts, by introducing what Redex calls a shortcut. This
little snippet of syntax gives meaning to the ==> arrow used
above for the notions of reduction by relating it to an overall
reduction arrow --> by placing the arguments to ==> in a
particular context.
Our primitive operators are given meaning by the delta
metafunction, which uses Redex’s ability to reach out to the host
language, Racket, to actually perform computations on base data
like numbers and strings.
Pairs and nil aren’t treated quite the same as numbers and
strings, since instead of being underlying Racket values, our
pairs are “stuck” terms with the cons constructor at the head of
the term.
At last, we have all the pieces we need to try out our language!
If you’re following along in
DrRacket, or have a
graphical environment available, try the following expression in
your REPL or IDE. It should open a window that lets you explore
the reduction of the term that we saw at the beginning of this article:
For noninteractive use such as test cases, Redex’s test-->>
function takes a term, reduces it as far as it will go, and
compares the result to another term.
Here we put a few simple unit tests in a testsubmodule,
so that they can be run by either DrRacket or by raco test on
the command-line.
We have built a model of a simple lambda-calculus based
programming language. We used the idea of evaluation contexts to
specify the evaluation order of arguments to a function and of
subterms of a begin expression. In Part II, we’ll
extend this language with some imperative actions, turning it into
a model of Actors.
Exercises
(Trivial.) Add a new primitive operator, string-append, to
the language.
(Easy.) By altering the definition of contexts, make the
evaluation order of this language right-to-left instead of
left-to-right.
(Medium-Hard.) What happens if we try to reduce a function call to a
function with duplicate names for its formal parameters, such as
((lambda (x x) x) 1 2)? Why does this happen? Alter the model so
that such terms become “stuck”.
Hint: This is “Medium-Hard” rather than just “Medium” because
you have to dig into exactly how Redex’s built-in substitution
machinery works: the naive fix of a call to Racket’s
check-duplicates
as a
side-condition
doesn’t immediately work.
(Easy.) Prove that this model is
deterministic (confluent):
For all expressions e, e′, and e″, if
e→e′ and e→e″ then e′=e″.
References and Footnotes
C. Hewitt, P. Bishop, and R. Steiger, “A universal
modular ACTOR formalism for artificial intelligence,” in Proc.
International Joint Conference on Artificial Intelligence,
1973, pp. 235–245.
Available online.↩
J. De Koster, T. Van Cutsem, and W. De Meuter, “43 Years of Actors: a
Taxonomy of Actor Models and Their Key Properties,” in Proc. AGERE, Amsterdam, The
Netherlands, Oct. 2016, pp. 31–40. doi: 10.1145/3001886.3001890.
Available online.↩
P. J. Landin, “The Next 700 Programming Languages,”
Commun. ACM, vol. 9, no. 3, pp. 157–166, 1966.
Available online.↩
An alert reader may spot that our grammar
here allows begin to have no subexpressions, (begin). The
reduction rules leave such terms stuck. We may wish to alter the
grammar to forbid them: the definitions of both begin and
lambda will have to change, as will spawn from parts II and
III. ↩
In their 1986 paper,6 the authors call
evaluation contexts “sk-contexts”; I asked Matthias about
this, and he said it was “a mistake” to call them this, and
that the terminology had been sorted out by the time of the
follow-up 1987 paper.7↩
M. Felleisen, D. P. Friedman, E. Kohlbecker, and B.
Duba, “Reasoning with Continuations,” in Proc. Symp. on Logic in
Computer Science, 1986, pp. 131–141.
Available online.↩↩2
M. Felleisen and D. P. Friedman, “A Reduction
Semantics for Imperative Higher-Order Languages,” in PARLE,
Parallel Architectures and Languages Europe, Volume II: Parallel
Languages, 1987, pp. 206–223.
Available online.↩↩2
M. Felleisen, R. B. Findler, and M. Flatt,
Semantics Engineering with PLT Redex. Cambridge,
Massachusetts: MIT Press, 2009. ↩
P. Stansifer. “Flexible binding-safe programming”.
PhD Dissertation, Northeastern University, 2016.
Available online.↩
Both Racket’s object
system
and its (separate!) generic interface
system
offer single-dispatch object-oriented programming: the choice of
method body to execute depends on the type of just one of the
arguments given to the method, usually the first one.
In some cases, the first thing that a method will do is to decide
what to do next based on the type of a second argument. This is
called double dispatch, and it has a long history in
object-oriented programming languages—at least as far back as the
original Smalltalk.
As an example, consider implementing addition for classes
representing numbers. A different method body would be needed
for each pair of representations of numbers.
I stumbled across the need for something like this when
implementing Operational Transformation (OT) for
Racket.
The macro
operation-transformer
in that code base is almost the double-dispatch macro from
this post; the difference is that for operational transformation,
the method concerned yields two results, and if the arguments
are switched on the way in, they must be switched on the way out.
Basic Double Dispatch
Here’s a basic double-dispatch macro:
(define-syntax-rule(double-dispatchop(arg1arg2)[pred?body...]...)(cond[(pred?arg2)body...]...[else(error'op"Unimplemented for ~v and ~v"arg1arg2)]))
It assumes that it will be used in a method where dispatch has
already been done on arg1, and that the next step is to inspect
arg2. It applies the pred?s in sequence until one of them
answers true, and then evaluates the corresponding body. If none
of the pred?s hold, it signals an error.
It’s often convenient to use it inside a class definition or
generic interface implementation with the following macros, which
simply define op to delegate immediately to double-dispatch.
The first is to be used with Racket’s object system, where the
first argument is bound implicitly to this and where predicates
should use Racket’s
is-a?
function. The second is to be used with Racket’s generic interface
system, where both arguments are explicitly specified and
predicates are more general.
For commutative operations like addition, it’s common to see the
same code appear for adding an A to a B as for adding a B to an A.
The next macro automatically flips its arguments and tries again
to see if B’s method has support for A, if it can’t find support
for B within A’s method. That way, code for combining B with A
need only be supplied in one place. It uses a
parameter
to keep track of whether it’s currently trying out a flipped pair
of arguments.
(definetrying-flipped?(make-parameter#f))(define-syntax-rule(commutative-double-dispatchop(arg1arg2)[pred?body...]...)(cond[(pred?arg2)(parameterize((trying-flipped?#f))body...)]...[(trying-flipped?)(error'op"Unimplemented for ~v and ~v"arg2arg1)][else(parameterize((trying-flipped?#t))(oparg2arg1))]))
Writing a simple wrapper works well for using
commutative-double-dispatch in a class definition:
but a wrapper for use with the generic interface system needs to
take care not to accidentally shadow the outer dispatch mechanism.
This macro uses
define/generic
to make op* an alias of op that always does a full dispatch on
its arguments:
Let’s see the system in operation! First, using Racket’s object
system, and then using Racket’s generic interfaces.
Example Scenario
We will first define two types of value foo and bar, each
responding to a single doubly-dispatched method, operator which
produces results according to the following table:
| foo | bar |
-----|-----|-----|
foo | foo | bar |
bar | bar | foo |
-----|-----|-----|
Then, we’ll extend the system to include a third type, zot,
which yields a zot when combined with any of the three types.
Some tests show that this is doing what we expect. Notice that we
get the right result when the first operand is a bar% and the
second a foo%, even though bar% only explicitly specified the
case for when the second operand is also a bar%. This shows the
automatic argument-flipping in operation.
Double dispatch is a useful addition to the object-oriented
programmer’s toolkit, and can be straightforwardly added to both
of Racket’s object systems using its macro facility.
This post was written as executable, literate Racket. You can
download the program from
here.
Unix famously represents all content as byte sequences. This was a
great step forward, offering a way of representing arbitrary
information without forcing an interpretation on it.
However, it is not enough. Unix is an incomplete design. Supporting
only byte sequences, and nothing else, has caused wasted effort,
code duplication, and bugs.
Text is an obvious example of the problem
Consider just one data type: text. It has a zillion character sets and
encoding schemes. Each application must decide, on its own, which
encoding of which character set is being used for a given file.
When applications get this wrong, both obvious bugs like
Mojibake and subtler flaws
like the
IDN homograph attack
result.
Massive duplication of code and effort
Lack of system support for text yields massive code duplication.
Rather than having a system-wide, comprehensive model of text
representation, encoding, display, input, collation, and comparison,
each programming language and application must fend for itself.
Because it is difficult and time consuming to properly handle text,
developers tend to skimp on text support. Where a weakness is
identified, it must be repaired in each application individually
rather than at the system level. This is itself difficult and time
consuming.
Inconsistent treatment
Finally, dealing only with byte sequences precludes consistent user
interface design.
The user cannot simply work with a file containing text; they must
make a decision about which encoding to use. Woe betide them if they
choose incorrectly.
A consistent approach would separate the question of text encodings
entirely from application-specific UIs. System UI for transcoding
would exist in one place, common to all applications.
User frustration
A tiny fraction of the frustration this kind of thing causes is
recorded in Thunderbird’s
bug 117236.
Notice that it took fourteen years to be fixed.
Ubiquitous problem
This Thunderbird change is just one example. Each and every
application suffers the same problems, and must have its text support
repaired, upgraded, and enhanced independently.
It’s not only a Unix problem. Windows and OS X are just as bad. They,
too, offer no higher-level model than byte sequences to their
applications. Even Android is a missed opportunity.
Learn from the past
Systems like Smalltalk, for all their flaws, offer a
higher-level model to programmers and users alike. In many cases, the
user never need learn about text encoding variations.
Instead, the system can separate text from its encoding.
Where encoding is relevant to the user, there can be a single place
to work with it. Contrast this with the many places encoding leaks
into application UIs today, just one of which is shown in the
Thunderbird example above.
It’s not just text
Text is just one example. Pictures are another. You can probably think
of more.
Our operating systems do not support sharing of high-level
abstractions of data between documents or applications.
An operating system with a mechanism for doing so would take a great
burden off both programmers and users.
Let’s start thinking about what a better modern operating system
would look like.
Programming language designers often need to experiment with syntax
for their new language features. When it comes to Javascript, we rely
on language preprocessors, since altering a Javascript engine
directly is out of the question if we want our experiments to escape
the lab.
Ohm is “a library and domain-specific language for parsing and
pattern matching.” In this post, I’m going to use it as a Javascript
language preprocessor. I’ll build a simple compiler for ES5 extended
with a new kind of for loop, using Ohm and the ES5 grammar
included with it.
All the code in this post is available in a Github repo.
Our toy extension: “for five”
We will add a “for five” statement to ES5, which will let us write
programs like this:
forfiveasx{console.log("We have had",x,"iterations so far");}
The new construct simply runs its body five times in a row, binding a
loop variable in the body. Running the program above through our
compiler produces:
for (varx=0;x<5;x++){console.log("We have had",x,"iterations so far");}
Extending the ES5 grammar
We write our extension to the ES5 grammar in a new file for5.ohm as
follows:
For5 <: ES5 {
IterationStatement += for five as identifier Statement -- for5_named
five = "five" ~identifierPart
as = "as" ~identifierPart
keyword += five
| as
}
Let’s take this a piece at a time. First of all, the declaration For5
<: ES5 tells Ohm that the new grammar should be called For5, and
that it inherits from a grammar called ES5. Next,
IterationStatement += for five as identifier Statement -- for5_named
extends the existing ES5 grammar’s
IterationStatement
nonterminal with a new production that will be called
IterationStatement_for5_named.
Finally, we define two new nonterminals as convenient shorthands for
parsing the two new keywords, and augment the existing
keyword
definition:
five = "five" ~identifierPart
as = "as" ~identifierPart
keyword += five
| as
There are three interesting points to be made about keywords:
First of all, making something a keyword rules it out as an
identifier. In our extended language, writing var five = 5 is a
syntax error. Define new keywords with care!
We make sure to reject input tokens that have our new keywords as a
prefix by defining them as their literal text followed by
anything that cannot be parsed as a part of an identifier,
~identifierPart. That way, the compiler doesn’t get confused by,
say, fivetimes or five_more, which remain valid identifiers.
By making sure to extend keyword, tooling such as syntax
highlighters can automatically take advantage of our extension, if
they are given our extended grammar.
Translating source code using the new grammar
First, require the ohm-js NPM module and its included ES5 grammar:
Next, load our extended grammar from its definition in for5.ohm, and
compile it. When we compile the grammar, we pass in a namespace that
makes the ES5 grammar available under the name our grammar expects,
ES5:
Finally, we define the translation from our extended language to plain
ES5. To do this, we extend a semantic function, modifiedSource,
adding a method for each new production rule. Ohm automatically uses
defaults for rules not mentioned in our extension.
Each parameter to the IterationStatement_for5_named method is a
syntax tree node corresponding positionally to one of the tokens in
the definition of the parsing rule. Accessing the asES5 attribute of
a syntax tree node computes its translated source code. This is done
with recursive calls to the modifiedSource attribute where required.
Our compiler is, at this point, complete. To use it, we need code to
feed it input and print the results:
>compileExtendedSource("for five as x { console.log(x); }");'for (var x = 0; x < 5; x++) { console.log(x); }'
Discussion
This style of syntactic extension is quite coarse-grained: we must
translate whole compilation units at once, and must specify our
extensions separately from the code making use of them. There is no
way of adding a local syntax extension scoped precisely to a block
of code that needs it (known to Schemers as
let-syntax).
For Javascript, sweet.js offers a more Schemely
style of syntax extension than the one explored in this post.
Mention of sweet.js leads me to the thorny topic of
hygiene. Ohm is a
parsing toolkit. It lets you define new concrete syntax, but doesn’t
know anything about scope, or about how you intend to use identifiers.
After all, it can be used for languages that don’t necessarily even
have identifiers. So when we write extensions in the style I’ve
presented here, we must write our translations carefully to avoid
unwanted capture of identifiers. This is a tradeoff: the broad
generality of Ohm’s parsing in exchange for less automation in
identifier handling.
Ohm’s extensible grammars let us extend any part of the language, not
just statements or expressions. We can specify new comment syntax, new
string syntax, new formal argument list syntax, and so on. Because Ohm
is based on
parsing expression grammars,
it offers
scannerless parsing.
Altering or extending a language’s lexical syntax is just as easy as
altering its grammar.
Conclusion
We have defined an Ohm-based compiler for an extension to ES5
syntax, using only a few lines of code. Each new production rule
requires, roughly, one line of grammar definition, and a short method
defining its translation into simpler constructs.
You can try out this little compiler, and maybe experiment with your
own extensions, by cloning its Github repo.
I ran some very quick-and-dirty informal experiments on my Acer C720
laptop, which is running a 64-bit Debian system and has 2GB RAM and a
two-core Celeron 2955U at 1.40GHz.
I measured approximate alist and hash table performance for
fixnum keys, using eq? as the lookup predicate (so assq, hasheq) (fixnum program)
length-64 byte vector keys, using equal? as the lookup predicate (so assoc, hash) (byte-vector program)
Each chart below has four data series:
probe/alist, average time taken to search for a key that is present in an alist
probe/alist/missing, average time taken to search for a key that is not present in an alist
probe/hasheq or probe/hash, average time taken to search for a key that is present in a hash table
probe/hasheq/missing or probe/hasheq/missing, average time taken to search for a key that is not present in a hash table
Fixnum keys
Here are average timings for fixnum keys:
Things to note:
Alists are here always faster than hasheq tables for 7 keys or
fewer, whether the key is present or not.
When the key is present in the lookup table, alists are on
average faster up to around 14 keys or so.
Length-64 byte vector keys
Here are average timings for length-64 random byte vector keys:
Things to note:
Alists are here always faster than hasheq tables for 4 keys or
fewer, whether the key is present or not.
When the key is present in the lookup table, alists are on
average faster up to around 16 keys or so.
Conclusions
Alists will be faster when you have very few keys - for eq?, around
seven or fewer, or for equal?, perhaps only as many as four,
depending on the size of each key.
If you expect with high probability that a given key will be present
in the table, the picture changes slightly: then, alists may be faster
on average up to around perhaps fifteen keys. Specifics of the
insertion order of your keys will naturally be very important in this
case.
gRPC looks very interesting. From a quick
browse of the site, it looks like it differs from CORBA primarily in
that
It is first-order.
It eschews exceptions.
It supports streaming requests and/or responses.
(That’s setting aside differences between protobufs and GIOP.)
It’s the first point that I think is likely to be the big win. Much of
the complexity I saw with CORBA was to do with trying to pass object
(i.e. service endpoint) references back and forth in a transparent way.
Drop that misfeature, and everything from the IDL to the protocol to the
frameworks to the error handling to the implementations of services
themselves will be much simpler.
The way streaming is integrated is interesting too. There’s a clear
separation between (finite) data, including lists/arrays, in the
protobuf message-definition language, and (possibly non-finite) behavior
in the gRPC service-definition language. Streams, being coinductive, fit
naturally in the service-definition part.
From time to time, one stumbles across a website that has gone out of
its way to make it difficult to save images displayed on the page. A
common tactic is to disable right-click for the elements concerned.
From time to time, I make binary snapshot downloads of the RabbitMQ
plugins that I have developed. You will be able to find the most
recent downloads from this page. I sign the downloadable files with
my public key.