Приховування деталей реалізаціі з Flow за допомогою псевдоніму непрозорого типу (Opaque Type Aliases)

Ви коли-небудь хотіли б, щоб ви могли приховати свої дані про виконання у своїх користувачів?

Ну, тепер всі ваші мрії нарешті збулися! Flow 0.51.0 додано підтримку для псевдонімів непрозорих типів (Opaque Type), з підтримкою babel, що надходить наступного тижня або близько того. Псевдоніми непрозорого типу є псевдонімами типу, які приховують їх основний тип. Ви можете бачити лише основний тип непрозорого типу (Opaque Type) у файлі, який оголошує непрозорий тип. Вони вже описані тут , тому ми проведемо решту цього повідомлення в блозі, показуючи, наскільки потужні псевдоніми мають непрозорі типи (Opaque Type).

Підтримка інваріантів з непрозорими типами (Opaque Type)

Неактивні типи псевдонімів дійсно корисні для підтримки інваріантів у вашому коді. Кожного разу, коли ви хочете висловити “речі типу T, де X є істинним”, ви можете розглянути питання про використання непрозорого типу псевдоніму (Opaque Type).

Як простий приклад, розглянемо тип для невід’ємних чисел:

NonNeg.js:
// @flow
opaque type NonNeg = number;

Тепер ми можемо виконувати деякі функції для взаємодії з NonNeg номерами:

NonNeg.js:
// @flow
opaque type NonNeg = number;
export function add(x: NonNeg, y: NonNeg): NonNeg {
  return x + y;
}
// Returns 0 at minimum
export function subtract(x: NonNeg, y: NonNeg): NonNeg {
  return Math.max(0, x - y);
}
export function zero(): NonNeg {
  return 0;
}
export function increment(x: NonNeg): NonNeg {
  return x + 1;
}

Створення непрозорого типу псевдоніма (Opaque Type) – це подібно до укладення договору з вашим користувачем: якщо вони погоджуються використовувати вашу бібліотеку лише для взаємодії з вашим типом, ви будете зберігати будь-який інваріантний ваш тип гарантії.

Ваші користувачі тепер можуть вийти і використовувати ваш тип, завжди знаючи, що це не буде негативним числом.

imports.js:
// @flow
import {add, subtract, zero} from './NonNeg';
const w = zero();
const x = increment(w);
const y = add(w, x);
const z = subtract(y, w);

Тепер, мабуть, один з наших користувачів намагається порушити наш інваріант:

bad-usage.js:
// @flow
import {zero} from './NonNeg';
let a = zero();
let b: NonNeg = a - 1;

Оскільки ми використовували псевдонім непрозорого типу (Opaque Type), який не виявляє його тип за межами визначального файлу, він отримає помилку:

Error: a = a - 1;
           ^ Error: a is NonNeg, which is incompatible with number.

Чудово! Ми змогли використати Flow, щоб захистити наш NonNeg інваріант! Але це може бути надто обмеженим. Врешті-решт, неотрицательный номер все ще є числом, тому ми повинні мати можливість використовувати його як такий.

Обмеження підтипу

Ми можемо додати обмеження підтипу до нашого непрозорого псевдоніма типу, щоб висловити те, що воно все ще може використовуватися як число поза файлу.

NonNeg.js:
// @flow
opaque type NonNeg: number = number;
/* ... */

Додавання : number до нашого непрозорого типу псевдоніму повідомляє Flow, що кожен NonNeg є number. Проте це не означає, що кожен number має значення NonNeg.

Тепер давайте ще раз поглянемо на код клієнта. Є ще помилка!

let b: NonNeg = a - 1;
                ^ Error: number. This type is incompatible with
let b: NonNeg = a - 1;
       ^ opaque type `NonNeg`.

Зверніть увагу, що тепер потоки інтерпретуються a — 1 як число, але не дозволятимуть його присвоювати змінній, яка є NonNeg. Якщо ми хочемо використовувати значення a як число, нам доведеться використовувати його в тому місці, де очікується число.

let b: number = a - 1; // Ok!

Щоб продовжити метафору, користувач порушує ваш договір. Як тільки вони припиняють виконувати функції вашої бібліотеки, ви більше не гарантуєте, що номер не отримає негативного значення.

Ще кілька випадків використання

  • Ви використовуєте рядки, щоб представляти унікальні ідентифікатори у вашій програмі? Не всі рядки є ідентифікаторами – ви можете використовувати псевдонім непрозорого типу, щоб переконатися, що жодні випадкові рядки не передаються навколо прикидаються ідентифікаторами!
  • У вас є два псевдоніми типу з однаковими основними типами, але зовсім іншими способами? Якщо ви зробите їх непрозорими, ви не зможете замінити їх на інший!
  • Чи ви уважно стежите за складними інваріантами у вашій структурі даних і хочете захистити користувачів від їх руйнування? Огоронить його за допомогою псевдоніму непрозорого типу!

Висновок

Непрозорі типи в Flow – це контракт із користувачами вашого типу. Контракт може бути вічно обов’язковим, якщо у вас немає іншого вибору, крім використання ваших функцій. Крім того, ви можете надати користувачеві витяг підтипу, який дозволяє користувачеві розірвати контракт, коли це буде потрібно.

Переклад статті “Hiding Implementation Details With Flow’s New Opaque Type Aliases Feature