Reason - новый язык программирования, поддерживаемый Facebook. Сам язык на самом деле является интерфейсом к OCaml, хорошо известному языку функционального программирования, который строго типизирован и поддерживает нативную компиляцию. Поддержка инструментов и инструментов разработчика для OCaml не очень удобна для новичков, что не позволяет многим заинтересованным разработчикам пробовать язык.

Reason, с другой стороны, пытается представить гораздо более простой синтаксис и по умолчанию компилируется в JavaScript, чтобы дать разработчикам более привычный рабочий процесс. В этой статье мы рассмотрим некоторые характерные особенности языка программирования Reason. Предыдущий опыт работы с OCaml будет полезен, но не является необходимым для понимания.

Установка

Первое, что я заметил, глядя на Reason, это то, как легко было начать работу, вы можете установить необходимые зависимости и среду выполнения, используя npm:

npm install -g bs-platform 
bsb -init my-first-app -theme basic-reason

а затем вы можете напрямую запустить приложение с помощью:

cd my-first-app 
npm run build
node src/demo.bs.js

Разработчики из мира node.js / JavaScript сразу почувствуют себя знакомыми с описанным выше рабочим процессом. Это огромное облегчение по сравнению с последним разом, когда несколько лет назад я попытался изучить OCaml для веб-разработки и мне пришлось установить OPAM и Ocsigen.

Поддержка IDE и редактора для Reason также неплохая с плагинами, доступными для VS Code, IDEA, Sublime Text и Atom. Я до сих пор помню время, когда единственным простым в использовании плагином редактора для OCaml был OcaIDE for Eclipse, и его было не так просто настроить.

Прежде чем мы сможем начать использовать плагины, нам нужно установить необходимые для них глобальные двоичные файлы.

Для macOS:

npm install -g https://github.com/reasonml/reason-cli/archive/3.0.4-bin-darwin.tar.gz

Для Linux:

npm install -g https://github.com/reasonml/reason-cli/archive/3.0.4-bin-linux.tar.gz

К сожалению, установка Reason в Windows все еще немного сложна, но вполне возможна.

Типы данных

В Reason есть несколько примитивных типов данных, которые хорошо знакомы тем, кто имеет опыт работы с OCaml.

Основные типы

Reason предоставляет обычные базовые типы для целых чисел, чисел с плавающей запятой, символов и строк. Одна вещь, которая может показаться новой, - это использование разных операторов для чисел с плавающей запятой и целых чисел, например +. и *. используются для сложения и умножения чисел с плавающей запятой.

Стандартная библиотека String в Reason включает все методы, присутствующие в OCaml Strings. И, поскольку строка Reason сопоставляется со строкой JavaScript, вы можете смешивать и сопоставлять строковые операции в обеих стандартных библиотеках. Следует отметить, что Reason предоставляет мощную систему типов с кортежами, вариантами и полиморфными типами. Мы должны избегать перегрузки строкового типа, как это часто бывает в динамически типизированном языке.

Тип кортежа

Тип кортежа (также называемый типом продукта) может помочь сгруппировать несколько типов вместе. Например, (int, string) обозначает пару типов int и string:

let pair: (int, string) = (2, "hello");
let v1 = fst(pair);
let v2 = snd(pair);

где предопределенные функции fst и snd могут использоваться для получения первого и второго элементов пары.

Тип записи

Тип записи похож на кортеж, где к полям в записи можно обращаться по имени, а не по положению. Например, мы можем определить точку, используя тип записи.

type point = {
  x: int,
  y: int,
};

Мы можем получить доступ к отдельным полям в записи с помощью оператора .:

let pt = {x: 1, y: 2};
pt.x; /* 1 */
pt.y; /* 2 */

Тип варианта

Тип варианта весьма полезен для выражения нескольких вещей:

/* Enumerated Type */
type color = Red | Blue | Green
/* Union Type */
type intorstr = I(int) | S(string)
/* Recursive Data Structures */
type num = Zero | Succ(num);
type intList = Nil | Cons(int, intList);

Язык также поддерживает дженерики через полиморфизм типов.

Полиморфный тип

Полиморфизм типов способствует повторному использованию кода, например мы можем определить список любого произвольного типа:

type list('a) =
  | Nil
  | Cons('a, list('a));
type intList = list(int);

Полиморфные типы фиксируются с использованием переменных типа, таких как 'a выше. Вот еще один пример того, как определить двоичное дерево:

type tree('a) = 
  | Leaf('a) 
  | Node(tree('a), tree('a));

Значения и тип ошибок

Язык предоставляет тип параметра, который можно использовать для значений ошибок.

type option('a) = None | Some('a);

Мы можем использовать тип опции всякий раз, когда у нас есть ситуация, которая может вызвать исключение. Например, мы можем использовать тип опции в случае ошибки деления на ноль:

let div = (a: int, b: int) : option(int) =>
  if (b === 0) {
    None
  } else {
    Some(a / b)
  };

Использование типа опции означает, что мы можем полностью избежать использования типа null в Reason, и, таким образом, чистая программа Reason не может иметь нулевых ошибок.

Выражения

Позволять

Конструкцию Let можно использовать для привязки локальных значений и функций. Пусть конструкции могут быть вложенными и ограниченными:

let foo = {
  let y = 1;
  let z = y + 0;
  y + z
};
/* y and z are not accessible here */

Конструкция let rec используется для создания рекурсивной функции:

let rec fact = (n) =>
  if (n == 0) {
    1
  } else {
    n * fact(n - 1)
  };

Если еще

Условные выражения могут быть выражены в Reason с помощью конструкции If-Else:

let max = (x, y) =>
  if (x > y) {
    x
  } else {
    y
  };

В Reason конструкция If-Else используется не так часто, поскольку язык обеспечивает сопоставление с образцом. Условное выражение - это просто синтаксический сахар для сопоставления с образцом для логических значений.

let max = (x, y) => switch(x > y) {
  | true => x
  | false => y
};

Выключатель

Сопоставление с образцом - это мощная функция Reason, которая обеспечивает способ деконструирования структур данных с помощью системы типов. Это более лаконично, чем использование условных выражений или методов деконструкции.

Сопоставление с образцом в причине предоставляется с помощью конструкции switch, вот пример для списков:

let rec sum = (xs) =>
  switch xs {
  | [] => 0
  | [x, ...xs] => x + sum(xs)
  };

Мы также можем произвольно сопоставлять выражения и шаблоны вложений, хотя мы все равно должны стараться их максимально сгладить, чтобы улучшить читаемость и избежать ошибок. В отличие от JavaScript, конструкция switch в Reason является исчерпывающей, поэтому компилятор будет жаловаться, если мы пропустим конкретный случай.

Петля

Циклы For полезны для выполнения императивного кодирования с побочными эффектами.

let print_numbers = (n) => {
  for (i in 1 to n) {
    print_int(i);
    print_string(" ")
  };
  print_newline()
};

Аналогичная конструкция существует и для циклов while.

while (testCondition) {   
   statements 
};

Обычно в функциональном языке, таком как Reason, императивные циклы не поощряются, но мы можем использовать их по мере необходимости, особенно если это делает программу более понятной.

Функции

Разум позволяет писать как чистые, так и нечистые функции. По возможности мы должны стараться использовать чистые функции и прибегать к написанию нечистых функций только при необходимости. Более того, неплохо было бы попытаться локализовать нечистые эффекты.

Помеченные аргументы

Если функция имеет несколько аргументов, мы можем использовать метки для ссылки на них. Это очень полезно, если аргументы одного типа, поскольку вы можете ссылаться на них по меткам при вызове функции:

let addCoordinates = (~x, ~y) => {   
   /* use x and y here */ 
}; 
... 
addCoordinates(~x=5, ~y=6);

Каррирование

Каррированная функция принимает один аргумент за раз и возвращает функцию, которая принимает следующий аргумент. В Reason функции могут автоматически вызываться частично:

let add = (x, y) => x + y;
let inc = add(1);
inc(10); /* 11 */

Рекурсивные функции

Мы можем определять рекурсивные (и взаимно рекурсивные) функции, используя конструкцию let rec. Например, рассмотрим метод ниже, который является хвостовой рекурсивной версией метода print_numbers и не требует использования циклов.

let print_numbers_rec = (n) => {
  let rec helper = (i) =>
    if (i > n) {
      print_newline()
    } else {
      print_int(i);
      print_string(" ");
      helper(i + 1)
    };
  helper(1)
};

Расширенные возможности

Модуль

Модули можно использовать для инкапсуляции типов, значений и методов. OCaml имеет одну из самых продвинутых модульных систем, доступных для использования с Reason.

Например, вот модуль для реализации стека целых чисел:

module StackInt = {
  type e = int;
  let stk = ref([]);
  let push = (x: e) => stk := [x, ...stk^];
  let pop = () =>
    switch stk^ {
    | [] => ()
    | [x, ...rest] => stk := rest
    };
};

Сигнатура модуля и реализация разделены, что позволяет определять абстрактные модули и предоставлять для них различные реализации. Таким образом, мы можем определить сигнатуру для модуля Locations и предоставить реализации для различных типов местоположений, таких как страны и города:

module type Locations = {
  type place;
  let getPlace: place => string;
};
module Countries: Locations = {
  type place = India | USA ;
  let getPlace = (p) => switch p {
    | India => "India"
    | USA => "USA"
  };
};
module Cities: Locations = {
  type place = Singapore | Delhi ;
  let getPlace = (p) => switch p {
    | Singapore => "Singapore"
    | Delhi => "Delhi"
  };
};

Обещать

Reason встроил поддержку обещаний JavaScript через модуль обещаний. Мы можем использовать оператор вертикальной черты |> для составления обещаний:

let doSomethingToAPromise = (somePromise) => {
  somePromise
  |> Js.Promise.then_(value => {
    Js.log(value);
    Js.Promise.resolve(value + 2)
  })
  |> Js.Promise.catch(err => {
    Js.log2("Failure!!", err);
    Js.Promise.resolve(-2)
  })
}

Взаимодействие с JavaScript

Одно из ключевых преимуществ Reason - хорошая совместимость с JavaScript. Мы используем все библиотеки, размещенные в реестре npm, а также вставляем произвольный код JavaScript в программу Reason, используя %bs.raw.

В качестве примера рассмотрим функцию jsCalculate ниже, она реализована на JavaScript. Большинство примитивных типов, таких как int, float, chars, strings и array, автоматически сопоставляются с соответствующими типами в Reason.

let jsCalculate: (array(int), int) => int = [%bs.raw
 {|
   function (numbers, scaleFactor) {
     var result = 0;
     numbers.forEach(number => {
       result += number;
     });
     return result * scaleFactor;
   }
 |}
];
let calculate = (numbers, scaleFactor) => jsCalculate(Array.of_list(numbers), scaleFactor);
Js.log(calculate([1, 2, 3], 10)); /* -> 60 */

В дополнение к пакетам node.js и OCaml есть несколько библиотек, доступных для использования в Индексе пакетов Reason (redex). Экосистема вокруг Reason стремительно развивается, поскольку разработчики начинают принимать язык за его строгую типизацию и простоту использования.

Надеюсь, статья оказалась для вас полезной и дала вам хорошее представление о языке Reason. Если вы хотите опробовать язык, зайдите в интерактивный интерактивный REPL здесь.