Previous Entry Share Next Entry
Complex QL Expressions
jducoeur wrote in querki_project
[More for the programmers.]

Today, not for the first time, I found myself writing a bit of QL for a page, and wanted to refactor it into a higher-order function. The problem is, while the underpinnings are there (so that I can write the built-in system functions), the QL language isn't good enough yet.

Then I went for a long out-and-about shopping trip, which gave me a lot of time to think -- which is always dangerous. Along the way, I figured out the correct approach to a *lot* of problems, including where I was horribly conflating two different things, and one where I've been making a bad assumption for months.

So here are a bunch of thoughts relating to how a *complex* QL function works. Again, none of this is for ordinary end users: this is puzzling out how a programmer would build libraries and functions to do more interesting things. Much of it should not be surprising to experienced functional programmers -- indeed, most of it boiled down to thinking a little less like a special snowflake and instead adopting some tried-and-true functional habits. None of it is set in stone, but it's all starting to make sense and feel right, so I want to write it down while it's fresh in my mind.

First and foremost, a small but terribly important change to something I said months ago: when you have a QL Expression with multiple Phrases in it, they do *not* all produce output. This was the bad assumption, and I realized today that I haven't ever *wanted* it to do so. Instead, only the *last* Phrase in the Expression produces an output -- which means that QL Expressions look almost exactly like functions in a typical functional language, where the last statement is the return value of the function.

So then why would you ever create a multi-Phrase Expression? For the same reason every functional language does: to set intermediate name bindings. Once the expression gets complicated enough, it becomes necessary to be able to give a name to the intermediate values; otherwise, things are unreadable.

Which brings us to Bound Names. Today's *huge* realization, as I drove home, was that there are two very different sorts of bindings, which act differently enough that I think we'll make them look different in the language.

The more straightforward kind of binding is a Bound Context. If I say:
... -> _thingy -> $myName
That assigns the name $myName to the context produced by _thingy. It's essentially the equivalent of a variable in many languages, but I'm specifically avoiding that term because it is *not* variable. We're going to follow functional best practices, and say that a name may only be bound once within an expression, and it results in an error otherwise.

(I believe there's a corollary that it should produce a warning if you end a non-final Phrase with anything *other* than a name binding or a side-effect.)

So you assign to a name by putting it at the end of a Phrase. You *use* it at the beginning of a Phrase:
$myName -> _thingy2 -> ...
I had spent a long time trying to decide whether I needed specific syntax to bind the name, until I realized that it's entirely about position. If a Bound Context is positioned at the beginning of a Phrase, it acts as a getter (and gives an error if it isn't set); otherwise, if it is at the end of a Phrase, it acts as a setter (and gives an error if it *is* set). Using a Bound Context in the middle of a Phrase is, I believe, always useless, and should give an error.

(This is the Querki version of the usual programming language question of which side of the equals sign something is on.)

There is one automatic Bound Context always present, named $context -- this is the context that was passed into this Expression at the beginning. (I suspect that being able to use this later in the expression will be very helpful.)

The other binding is Bound Phrases. These are *syntactic* bindings, and may well prove to be scary-powerful.

It's an advanced topic, but actually necessary for a lot of the things I'm doing because, as previously mentioned, function *parameters* always have to be passed by name rather than by value. Essentially, instead of passing in the value from evaluating the parameter, we are passing in the text of the parameter itself, and the *function* decides when and how to evaluate it.

Bound Phrases have names starting with "!". (I think -- we'll see. The choice of sigil is arbitrary.) To begin with, there will be numbered phrases for the parameters: !1, !2, and so on. Eventually, we'll provide the ability to name these parameters.

A Bound Phrase may be used anywhere inside of the Expression. It always means "take the received Context, apply this Phrase, and produce the result from that". So just to take a strawman example, I could write a function ApplyTwice, which you call like this:
... some text -> ApplyTwice(_indent)
It would simply be defined as:
!1 -> !1
That is, it takes the incoming context, applies the parameter (the _indent function) to it, and then does that again.

I only have one or two ways that I expect to use this in the nearish term, but the potential is considerable: this is the first step towards writing higher-order functions in QL. In the long run, I expect we'll be happier if we can do that straightforwardly...


Log in

No account? Create an account