Транзакционные пробные блоки в ValueScript

Apr 02 2023
Исключения трудно получить правильно. Особенно сложной проблемой, которую легко не заметить, является возможность частичного вычисления внутри блоков try.

Исключения трудно получить правильно. Особенно сложной проблемой, которую легко упустить из виду, является возможность частичного вычисления внутри tryблоков.

Рассмотрим следующую программу JavaScript:

let a = new SmallQueue(["item1"]);
let b = new SmallQueue(["item2", "item3", "item4"]);

try {
  const item = a.pop();
  b.push(item);
} catch (e) {
  console.error("Failed to move item from a to b", e);
}

console.log({ a, b });

{
  a: [],
  b: ["item2", "item3", "item4", "item1"],
}

{
  a: [],
  b: ["item2", "item3", "item4"],
}

let a = new SmallQueue(["item1"]);
let b = new SmallQueue(["item2", "item3", "item4"]);

try {
  if (b.isFull()) {
    throw new Error("b is full");
  }

  const item = a.pop();
  b.push(item);
} catch (e) {
  console.error("Failed to move item from a to b", e);
}

console.log({ a, b });

Другой вариант — сохранить копию переменных, которые могут измениться, и восстановить их catch:

let a = new SmallQueue(["item1"]);
let b = new SmallQueue(["item2", "item3", "item4"]);

const aCopy = a.copy();
const bCopy = b.copy();

try {
  const item = a.pop();
  b.push(item);
} catch (e) {
  a = aCopy;
  b = bCopy;
  console.error("Failed to move item from a to b", e);
}

console.log({ a, b });

  1. Вы можете не делать этого, потому что это многословно и кажется излишне оборонительным.
  2. Требуется copyопределить операцию
  3. Если copyоперация существует, она может быть неэффективной
  4. Если a.popесть побочные эффекты, эти побочные эффекты не будут отменены

let a = new SmallQueue(["item1"]);
let b = new SmallQueue(["item2", "item3", "item4"]);

let item;

try {
  item = a.pop();
} catch (e) {
  console.error("Failed to move item from a to b", e);
  console.log({ a, b });
  return;
}

try {
  b.push(item);
} catch (e) {
  a.unPop(item);
  console.error("Failed to move item from a to b", e);
}

console.log({ a, b });

  1. Чрезвычайно многословный
  2. a.unPop(item)требует от нас понимания, что эта операция нужна при b.push(item)сбое, что все равно легко пропустить

В ValueScript tryблоки являются транзакционными — они либо выполняются до завершения, либо возвращаются.

Наша исходная программа делает то, что мы хотели, без каких-либо модификаций:

let a = new SmallQueue(["item1"]);
let b = new SmallQueue(["item2", "item3", "item4"]);

try {
  const item = a.pop();
  b.push(item);
} catch (e) {
  console.error("Failed to move item from a to b", e);
}

console.log({ a, b });
{
  a: ["item1"],
  b: ["item2", "item3", "item4"],
}

  1. «Вы можете этого не делать»
    — компилятор ValueScript автоматически вставляет инструкции
  2. «Требуется copyоперация»
    — ValueScript использует семантику значений, он просто использует aCopy = a, и это эквивалентно копированию
  3. « copyможет быть неэффективным»
    — ValueScript полностью использует копирование при записи, это довольно эффективно
  4. «Побочные эффекты a.popне будут устранены»
    — ValueScript не имеет побочных эффектов

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

ValueScript — это диалект TypeScript с семантикой значений. У него есть онлайн-игровая площадка, включающая демонстрацию программы из этой статьи , и она с открытым исходным кодом .