В чем разница между оператором + и std.mergePatch в Jsonnet?

std.mergePatch Jsonnet реализует RFC7396, но в моем наивном тестировании я не нашел разницы между его поведением и оператором +; например оператор + соответствует синтаксису x+. std.mergePatch реализован в самом Jsonnet, что, по-видимому, подразумевает что он отличается от оператора +, который, как я предполагаю, является встроенным.

Чем отличается семантика этих двух способов слияния?


person Keith Pinson    schedule 06.05.2020    source источник


Ответы (1)


Jsonnet + и std.mergePatch — это совершенно разные операции. Оператор + работает только на одном уровне, а std.mergePatch рекурсивно обходит объект и объединяет вложенные объекты. Проще всего объяснить на примере:

local foo = { a: {b1: {c1: 42}}},
      bar = { a: {b2: {c2: 2}}};
foo + bar

Выход:

{
   "a": {
      "b2": {
         "c2": 2
      }
   }
}

Обратите внимание, что bar.a полностью заменил foo.a. С + все поля во втором объекте переопределяют поля в первом объекте. Сравните это с результатом использования std.mergePatch(foo, bar).

{
   "a": {
      "b1": {
         "c1": 42
      },
      "b2": {
         "c2": 2
      }
   }
}

Поскольку и foo, и bar имеют поле a, они объединяются, и окончательные результаты содержат как b1, так и b2.

Итак, повторюсь, + — это «плоская» операция, которая переопределяет поля первого объекта полями второго объекта.

Однако это не конец истории. Вы упомянули синтаксис field+: value, и я попытаюсь объяснить, что он на самом деле делает. В Jsonnet + — это не просто перезапись, а наследование в объектно-ориентированном смысле. Он создает объект, который является результатом наследования второго объекта от первого. Немного экзотично иметь для этого оператор — во всех основных языках такие отношения определены статически. В Jsonnet, когда вы делаете foo + bar, объект bar имеет доступ к материалам от foo до super:

{ a: 2 } + { a_plus_1: super.a + 1}

Это приводит к:

{
   "a": 2,
   "a_plus_1": 3
}

Вы можете использовать эту функцию, чтобы объединить поля глубже внутри:

{ a: {b: {c1: 1}, d: 1}} +
{ a: super.a + {b: {c2: 2} } }

В результате чего:

{
   "a": {
      "b": {
         "c2": 2
      },
      "d": 1
   }
}

Однако это немного повторяется (было бы раздражающим, если бы имя поля было длиннее). Итак, у нас есть хороший синтаксический сахар для этого:

{ a: {b: {c1: 1} , d: 1}} +
{ a+: {b: {c2: 2}} }

Обратите внимание, что в этих примерах мы выполнили слияние только для одного выбранного поля. Мы все же заменили значение a.b. Это намного более гибко, потому что во многих случаях вы не можете просто наивно объединить все внутри (иногда вложенный объект является «атомарным» и должен быть полностью заменен).

Версия в +: работает так же, как и версия с super. Небольшое отличие состоит в том, что +: на самом деле преобразуется в что-то вроде if field in super then super.field + val else val, поэтому оно также возвращает то же значение, когда super вообще не указано или не имеет этого конкретного поля. Например, {a +: {b: 42}} отлично оценивается как {a: { b: 42 }}.

Обязательная проповедь: несмотря на то, что + очень мощное средство, не злоупотребляйте им. Рассмотрите возможность использования функций вместо наследования, когда вам нужно что-то параметризовать.

person sbarzowski    schedule 07.05.2020