Combining objects with + (plus operator) vs whitespace

1.9k Views Asked by At

Jsonnet's docs mention that the + operator can be used for inheritance, or, as it's worded in the tutorial, to combine objects:

{
  a: 1,
  b: 2,
}
+
{
  a: 3
}

However, I've noticed that - at least in simple cases like the above - simply omitting the + operator and writing the two consecutive objects separated by whitespace compiles down to the same result. That is, there's no difference in output between the program above and this one:

{
  a: 1,
  b: 2,
}
{
  a: 3
}

I'm puzzled, because I haven't noticed any mention of this implicit object combination in the docs. I also notice that this behaviour seems to be unique to objects and doesn't happen with other types. (In particular, despite Jsonnet drawing inspiration from Python for some of its features, you can't implicitly concatenate strings with whitespace like you can in Python.)

Thus, some questions:

  • Is this even intended behaviour, or a bug?
  • Is it documented anywhere?
  • Are there any behaviour differences between combining objects with the explicit + operator and combining them implicitly with whitespace?
2

There are 2 best solutions below

1
On BEST ANSWER

A colleague nudges me towards a mention of this in the tutorial, in the Object Orientation section:

Let's make it more concrete by mixing deriving some cocktails that are quite similar from a template that draws out their similarities. The + operator is actually implicit in these examples. In the common case where you write foo + { ... }, i.e. the + is immediately followed by a {, then the + can be elided. Try explicitly adding the + in the 4 cases, below.

local templates = import 'templates.libsonnet';

{
  // The template requires us to override
  // the 'spirit'.
  'Whiskey Sour': templates.Sour {
    spirit: 'Whiskey',
  },

  // Specialize it further.
  'Deluxe Sour': self['Whiskey Sour'] {
    // Don't replace the whole sweetner,
    // just change 'kind' within it.
    sweetener+: { kind: 'Gomme Syrup' },
  },

  Daiquiri: templates.Sour {
    spirit: 'Banks 7 Rum',
    citrus+: { kind: 'Lime' },
    // Any field can be overridden.
    garnish: 'Lime wedge',
  },

  "Nor'Easter": templates.Sour {
    spirit: 'Whiskey',
    citrus: { kind: 'Lime', qty: 0.5 },
    sweetener+: { kind: 'Maple Syrup' },
    // +: Can also add to a list.
    ingredients+: [
      { kind: 'Ginger Beer', qty: 1 },
    ],
  },
}

So, implicit object combination:

  • is legal
  • is documented, but seemingly only in the tutorial and formal spec, currently, not in the language reference
  • has behaviour identical to +, except that it's only usable when the second operand starts with a {, not when it's a variable.
0
On

It works like this in the Google GCL and Borgcfg templating languages, too. The value of your first clause is a map/template object. The additional clause { a: 3 } is simply an override of an existing value in a preceding map/template object. I imagine this is what the designers also intended for jsonnet since it's also from Google.

Just an FYI the + operator in Borg/GCL does something much much worse it evaluates everything on both sides of the operator before combining the maps which makes it practically useless in most situations. In GCL/Borgcfg what you want usually is to take a template with functions inside, edit a few, and then instantiate it via new assignments to the variables that influence the functions, which would be impossible after the + operator because that welds/binds all the variables into the template right away before merging the maps -- too early -- making it of very little usefulness -- this is a mistake made by the early languages designers of GCL.