Previous Entry Share Next Entry
Querki, Evaluation Context, and Continuations: a motivating example
jducoeur wrote in querki_project
[This one's for the programmers. Indeed, this one's largely for the hardcore software engineers.]

One of the things that's unusual about the QL language is parameter-passing. Where almost every language I know normally uses call-by-value, at least by default, Querki defaults to *sort* of what Scala refers to as call-by-name. I started to refer to Querki's style as "Evaluation in Dynamic Context", but as we'll see below, I wound up changing my mind as I wrote this post.

The motivation, as with everything in QL, is that the language should Just Work in the most obvious cases. When I find that I *want* the language to do something, I'm working backwards to what that actually implies in terms of syntax. This evaluation style isn't the *most* radical thing about QL, but it's way up there.

The difference is in *when* you evaluate parameters, and *what* you evaluate them against. To understand what I'm talking about, let's take today's example.

My project for the past couple of days, as recently mentioned, was to add a Modification Timestamp to each thing. I added that to the database, and then added a _modTime method, which you can use on any Thing:
[[Thing -> _modTime -> Date and Time]]
That is, it receives a Thing, and produces a value (of the new "Date and Time Type", which is exactly a Joda-time DateTime) that gives that Thing's modification time. So far, so obvious.

But then I realized that what you *mostly* want this for is to sort Things by timestamp. The _sort method has existed for a while, and is very straightforward: the default (which is all that was implemented so far) is to sort by Display Name. For example, to sort all of my Pages alphabetically by Display Name and show them as a bullet list, I would say:
[[Page._instances -> _sort -> ""* ____""]]
So as usual, I asked myself, "How *should* sorting by _modTime work?". The answer, obviously, is this:
[[Page._instances -> _sort(_modTime) -> ""* ____""]]
Seems pretty intuitive, and in fact it works exactly as expected -- I'm simply giving _modTime as the parameter to the _sort, and it defines what we're sorting by. But when you look at that from the viewpoint of a typical programming language, there is something *very* strange going on here.

Consider: what would the above mean in a language like Java? _modTime would be evaluated in the *calling* context -- it would return the modTime of the object that is defining the expression. But that's not what we want in a data-processing language like QL, where everything is about the context that is being passed into each Stage. We actually want _modTime to be evaluated against *each Thing in the pipeline Context*. And that is, in fact, the way things usually work in QL: parameters are typically evaluated against each incoming value, to produce a result that is what is actually used by the method.

Now, the serious language geeks in the audience are going, "Hey, waitaminnit -- this is sounding familiar". I confess, it took me until today (indeed, about an hour ago) to realize what's going on here. QL Methods aren't actually methods in the conventional sense -- they are actually approximately Continuations. Parameters in QL are essentially *never* ordinary pass-by-value ones as you expect in a conventional language -- they are actually always full QL Expressions, which are evaluated in the local context where and when the method wants them to be. And a "Method" is actually defining a transformation that is applied to each incoming value, with the ability to optionally modify those values using the parameters.

In other words, the _sort method is defined as (roughly), "Take each Thing in the received context, apply the parameter to them, and sort by the result". The method's parameter isn't ordinary passed-in information -- the method knows that its parameter is a code block that it can use to transform the received data, and it defines when that happens and what to do with it.

Actually, it's even crazier than that. In order to support some of the functionality I want (specifically, the _code() method), the method is allowed to treat the parameter at the *syntactic* level. I'm not sure precisely how this will be exposed to user-written methods, but the implication is that you can treat methods as either Continuations *or* Macros, by tweaking the way you call the parameter.

This stuff is all still very fluid, but the project still manages to surprise me on a regular basis. By deliberately turning off my usual "How do normal programming languages work?" filter, and replacing it with, "What seems to be simplest and most obvious?", I seem to be evolving QL into an *insanely* powerful language, where advanced concepts like Continuations just fall out completely naturally. It's really rather (ahem) cool...


Log in

No account? Create an account