Skip to content

Co-expressions #3388

@01mf02

Description

@01mf02

@wader made me initially aware of @nicowilliams's co-expressions PR.
I'm not sure yet whether I understand it correctly. But if what follows is not what that PR is about, I think that it would still be interesting on its own sake.

So what I understand is that a co-expression allows you to capture all outputs of an expression into a variable, such that each further occasion of that variable yields the next output of the expression.
For example:

(1, 2, 3) as $@s | ($@s, $@s)

would yield 1, 2.
(I just made up a bit of syntax. I think that @nicowilliams uses just @s to denote a "co-variable", but this might cause conflicts with the escaping syntax @uri "foo\(.)bla". Let's not bikeshed for now, that can be done later. ^^)

I was quite excited to realise that you can implement zip/2 quite easily with such a construct, which you cannot do with current jq:

def zip($@x; $@y): def rec: $@x as $x | $@y as $y | [$x, $y]; rec;

(I suppose that if the expression that has been captured by a co-variable $@x does not yield any more outputs, then $@x simply yields empty.)

Furthermore, this would allow us to implement inputs / input very naturally.
In particular, just like $ENV is currently bound on the top-level in every jq program, we could bind $@input on the toplevel, which would allow us to write:

def inputs: $@input | ., inputs;
def input : $@input;

And now for something completely different: foreach. I found that we can even simulate that with co-expressions:

foreach xs as $x (init; update; project) ===
  init as $init | xs as $@x | $init |
  def rec: label $exit | ifempty($@x; break $exit) as $x | update($x) | (project($x), rec); rec

Here, isempty(f; g) is a bit like f // g, except that it calls g only if f is empty, otherwise it returns all outputs of f.
Of course, this version of foreach is not as good performance-wise as the built-in foreach, e.g. because the label breaks tail recursion and so on. I'm not proposing here to replace the built-in foreach by what is here. But it is at least a very good sign that you can simulate built-in operators with this approach which you could not even closely simulate before.

All in all, I am pretty excited about this. Even if it would definitely change the characteristics of jq towards a language with side effects, I think that the gained power could be worth it. I also think that I could implement this in my jq implementation jaq with quite little effort.

If somebody would extract the relevant parts of @nicowilliams's PR and make a smaller PR out of it that contains only to co-expression functionality, then I think I would be motivated to go ahead and implement this in jaq.

What do you think?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions