Формулы


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

В этой статье мы разберем основы формул.

Типы данных

Числа (number)

Целые числа: 123, -34, 0 и т.д.

Дробные числа: 10.22, 0.01 и т.д.

Поддерживается математическая запись для очень больших и очень маленьких чисел:

1.602176565e-19, 6.02214129e+23 и т.д.

Особые значения чисел: Infinity (бесконечность) и NaN ("Not a Number", может возникать в тригонометрических функциях).

Советы по работе с числами

  1. Используйте только целые числа, по возможности избегайте дробных. Даже цены лучше хранить в копейках (150 копеек вместо 1.5 рублей). Причина проста — дробные числа порождают артефакты. Классический пример это 0.1 + 0.2 = 0.30000000000000004; в причины углубляться не будем, знайте только, что это может проявиться тогда, когда вы этого не ждете, и где вы забыли поставить округление, чтобы избавиться от артефактов.
  2. Не работайте с огромными числами. Потому что там в какой-то момент тоже начинается потеря точности. Например, если к числу 10000000000000000 прибавить 1, получится... 10000000000000000. Так работает погрешность в больших числах. Безопасно работать с целыми числами в диапазоне от минус до плюс 9 007 199 254 740 991.

Строки (string)

Заключаются в одинарные (') или в двойные кавычки ("). Разного рода типографские кавычки (вроде , , , и т.д.) не принимаются.

Примеры: "Hello!", "123", 'Test' и т.д.

Если внутри строки встречается такая же кавычка, как та, что используется для формирования строки, перед ней нужно поставить знак \.

Пример: "Это просто \"чудо\" какое-то". Или заключить строку в кавычки другого типа, чтобы не было конфликта кавычек одного типа: 'Это просто "чудо" какое-то'.

Истина или ложь (boolean)

Или true, или false, всего 2 возможных значения. Обычно true означает нечто положительное, а false — нечто отрицательное. Да/нет, включено/выключено, в наличии/отсутствует и так далее.

Массивы (array)

Массив или список — это набор значений, разделенных запятыми, заключенный в квадратные скобки: [1, 2, 3], ["a", "b", "c"] и т.д.

Массив может содержать значения разных типов: [1, "b", false], включая вложенные массивы: [1, [12, 13], ["a", "b"]].

Объекты (object)

Объект — это набор пар ключ-значение, разделенных запятыми, заключенный в фигурные скобки. Значение может быть любого типа, а ключом может быть только строка. Между ключом и значением всегда ставится двоеточие: {"x": 12, "y": 44}, {"type": "house", "price": 1200} и т.д.

Если в названии ключа используются только латинские буквы, цифры (но не в самом начале) и символ _, то ключ можно не заключать в скобки: {name: "John", age: 34}.

Как и массивы, объекты могут содержать другие вложенные объекты. Ключи в объекте не должны повторяться, но если такое случается, то "верным" значением считается последнее. То есть в объекте {x: 1, x: 2} значением ключа x будет 2.

Даты (date)

Даты записываются между двумя знаками #, в формате "год-месяц-день": #2022-04-22#, #1994-05-04# и т.д. Ведущий ноль обязателен, то есть #1994-5-4# это уже неправильный формат.

Дата может включать также и время:

#2022-04-22 14:05#

#2022-04-22 14:05:15# (с секундами)

#2022-04-22 14:05:15.245# (с миллисекундами)

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

#2022-04-22 14:30 +05# (Нью-Йорк)

#2022-04-22 14:30 -03# (Москва)

Можно указать часовой пояс, опустив время:

#2022-04-22 -03# (00:00 по Москве)

Больше примеров

#2022-04# (только год и месяц, то же что #2022-04-01 00:00#)

#2022-07-02T15:22:35.313Z# (формат ISO 8601)

#2022-04-22 14:30:15 -09:30# (часовой пояс с минутами)

Тип null (null)

Означает отсутствующее значение. Единственным возможным значением этого типа данных является null.

Переменные

Переменные обеспечивают доступ к значениям, которые могут меняться. Переменная может содержать значение любого типа — число, объект, массив, дату и т.д.

Название переменной может содержать латинские буквы, цифры и знак "_", но не может начинаться с цифры. Несколько примеров названий переменных: member_id, url, test123.

Есть возможность объявлять переменные для использования внутри формулы.

Пример

text = 'Это текст, который обрезается по длине';
length = 20;

if(
  length(text) > length,
  substr(text, 0, length) & '…',
  text
)

В значениях переменных также можно использовать формулы. Общий синтаксис такой:

название_переменной = формула;
название_переменной2 = формула;
...
формула

Доступ к элементам массива

Если в переменной хранится массив, доступ к элементам осуществляется следующим образом: array[0], где 0 это номер элемента.

Обратите внимание, что нумерация начинается с 0! Первый элемент, это элемент под номером 0, второй элемент под номером 1 и так далее.

Допустим, у нас есть переменная prices, содержащая массив [25, 33, 11]. Получить второе значение массива можно следующим образом: prices[1].

Также есть удобный способ получения последнего элемента: prices[-1], то есть при использовании отрицательного номера, элементы считаются с конца.

Доступ к значениям объекта

Похожим образом осуществляется и доступ к значениям объекта, только вместо номера в квадратных скобках мы передаем строку: object["a"], где "a" это ключ.

Допустим, у нас есть переменная man, которая содержит объект { "first name": "John", "age": 34 }. Получить значение "first name" этого объекта можно следующим образом: man["first name"].

А так получаем значение "age": man["age"]. Если ключ содержит только латинские буквы, цифры и символ "_", то синтаксис можно упростить: man.age.

Комментарии

В код формулы можно вставлять комментарии двумя способами.

Первый — конструкция /* комментарий */, которая может быть многострочной, а может занимать только часть строки.

Пример:

myDate + /* сутки */ 86400000

Часть кода /* сутки */ будет проигнорирована при выполнении формулы.

Второй — конструкция // комментарий, которая превращает в комментарий всю оставшуюся часть строки, и которую удобно использовать при перечислении элементов массива или значений объектов.

Пример:

sum([
	10,
	20,
	// 30,
	40,
])

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

Операторы

Оператор — это простейшая конструкция языка.

Сложение и вычитание: +, -

  • 2 + 24
  • 6 - 33

Порядок операций важен, но можно использовать скобки:

  • 5 - 2 + 58
  • 5 - (2 + 5)-2

Оператор сложения нельзя использовать со строками. Для этого есть специальный оператор объединения строк (&).

Оба оператора умеют работать с датой. В этом случае они прибавляют или вычитают миллисекунды.

  • #2012-04-21 14:12:00# + 5#2012-04-21 14:12:00.005#

В сутках 86400000 миллисекунд (1000 миллисекунд * 60 секунд * 60 минут * 24 часа), так можно прибавлять или вычитать дни:

  • #2012-04-21 14:12:00# + 86400000#2012-04-22 14:12:00#
  • #2012-04-21 14:12:00# - 86400000#2012-04-20 14:12:00#

Умножение и деление: *, /

  • 25 * 4100
  • 10 / 52

Приоритет умножения и деления выше, чем у сложения, то есть умножение всегда выполняется первым.

  • 2 + 2 * 26
  • 2 * 2 + 26

Но тут тоже можно использовать скобки:

  • (2 + 2) * 28

Операторы сравнения: ==, !=, >, >=, <, <=

Оператор "равно": ==

  • 10 == 20true
  • "10" == 20false
  • [1, 2, 3] == [1, 2, 3]true
  • [1, 2, 3] == [1, 3, 2]false

Оператор "не равно": !=

  • 2 != 4true
  • "4" != 4true
  • true != truefalse

Оператор "больше": >

  • 56 > 12true
  • "b" > "a" ⇒ true

Оператор "больше или равно": >= — работает как оператор "больше", но допускает также равенство.

Оператор "меньше": <

  • 45 < 111true
  • "e" < "z"true

Оператор "меньше или равно": <= — работает как оператор "меньше", но допускает также равенство.

Остаток от деления: %

  • 10 % 31
  • 5 % 75
  • 7 % 52

Объединение строк: &

  • "abc" & "def""abcdef"
  • 24 & 55"2455"

Логический оператор and

Возвращает true, если И левое, И правое выражение равняются true.

  • 15 > 10 and 15 < 20true
  • 125 > 10 and 125 < 20false

Логический оператор or

Возвращает true, если левое ИЛИ правое выражение равняются true.

  • 15 > 10 or 15 < 20true
  • 125 > 10 or 125 < 20true

Значение по умолчанию: ??

Позволяет указать значение, которое будет использовано, если переменная, элемент массива или свойство объекта равно null.

  • null ?? "default""default"
  • 123 ?? "default"123

Этот оператор удобно использовать при обращении к элементам массивов или значениям объектов, которых может не существовать.

  • array[1] ?? "something""something", если array[1] не существует или равен null
  • object.key ?? "something""something", если object.key не существует или равен null

Значение или null: ?

Является сокращенной версией оператора ??, в котором значением по умолчанию является null.

Наличие элемента в массиве: in

  • 2 in [1, 2, 3]true
  • 22 in [1, 2, 3]false

Функции

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

Первый — передать значение в качестве первого аргумента функции:

locate('Hello, world!', 'Hello')

Второй — выполнить функцию над значением через точку:

'Hello, world!'.locate('Hello')

Оба способа эквивалентны.

Математические функции

sum(numbers)

Складывает все числа в массиве.

sum([1, 2, 3]) == 6

multiply(numbers)

Умножает между собой все числа в массиве.

multiply([2, 3, 4]) == 24

abs(number)

Возвращает абсолютное значение числа.

abs(12) == 12
abs(-44) == 44

round(number, precision?)

Округляет число с заданной точностью.

round(12.46) == 12
round(12.46, 1) == 12.5
round(12.462, 2) == 12.46
round(12.46, -1) == 10
round(123456, -3) == 123000

Используется округление 0.5 до ближайшего четного числа.

round(12.5) == 12
round(13.5) == 14
round(14.5) == 14
round(15.5) == 16

trunc(number, precision?)

Отсекает дробную часть с заданной точностью.

trunc(12.46) == 12
trunc(12.46, 1) == 12.4
trunc(12.462, 2) == 12.46
trunc(12.46, -1) == 10
trunc(123456, -3) == 123000

floor(number)

Округляет число вниз.

floor(12.7) == 12
floor(-12.7) == -13

ceil(number)

Округляет число вверх.

ceil(12.3) == 13
ceil(-12.3) == -12

random()

Генератор случайных чисел. Возвращает дробные числа в диапазоне от 0 до 1:

random() == 0.2850926978256794
random() == 0.7654290091085383

Это дробное число можно преобразовать в целое число в рамках заданного диапазона.

Целое число в диапазоне от 0 до 10 (включая 0 и 10):

round(random() * 10) == 7
round(random() * 10) == 3
round(random() * 10) == 10
round(random() * 10) == 5

Целое число в диапазоне от 10 до 20:

10 + round(random() * (20 - 10)) == 12
10 + round(random() * (20 - 10)) == 10
10 + round(random() * (20 - 10)) == 17
10 + round(random() * (20 - 10)) == 18

Помните, что случайные числа могут не всегда выглядеть случайными.

min(numbers)

Возвращает минимальное значение из списка.

min([6, 2, 3]) == 2
min([0, -44, -2]) == -44

Функция умеет работать с любыми типами, а не только с числами.

min(["b", "e", "a"]) == "a"
min([#2012-05-22#, #2012-05-21#]) == #2012-05-22#

max(numbers)

Возвращает максимальное значение из списка.

max([3, 6, 2]) == 6
max([-3, 0, -2]) == 0

Функция умеет работать с любыми типами, а не только с числами.

max(["b", "e", "a"]) == "e"
max([#2012-05-22#, #2012-05-25#]) == #2012-05-25#

pow(number, exponent)

Возводит число в указанную степень.

pow(2, 2) == 4
pow(2, 3) == 8
pow(3, 2) == 9
pow(3, 3) == 27

sqrt(number)

Возвращает квадратный корень числа.

sqrt(4) == 2
sqrt(25) == 25

log(number, base)

Возвращает логарифм числа. Логарифм — это показатель степени, в которую надо возвести число, называемое основанием, чтобы получить данное число.

log(100, 10) == 2
log(16, 2) == 4

exp(number)

Возвращает значение выражения «E в степени number», где e — число Эйлера, основание натурального логарифма.

Тригонометрия

acos, acosh, asin, asinh, atan2, atan, atanh, cos, cosh, sin, sinh, tan, tanh.

Работа с текстом

length(string)

Возвращает количество символов.

length('') == 0
length('abcd') == 4

locate(string, substring, start?)

Возвращает позицию первого вхождения подстроки.

locate('Hello, world!', 'Hello') == 0
locate('Hello, world!', 'world') == 7

Аргумент start позволяет указать позицию, откуда начинать поиск.

locate('Hello, world!', 'o') == 4
locate('Hello, world!', 'o', 5) == 8

Если подстрока не найдена, результатом будет -1.

locate('Hello, world!', 'something') == -1

substr(string, start, count?)

Возвращает подстроку.

substr('Hello, world!', 7) == 'world!'

Аргумент count позволяет ограничить количество символов подстроки.

substr('Hello, world!', 7, 3) == 'wor'

join(strings, separator?)

Объединяет все элементы массива в одну строку.

join(['a', 'b']) == 'ab'
join(['a', 123, null, false]) == 'a123false'

Аргумент $$delimiter используется в качестве разделителя:

join(['a', 'b', 'c'], ':') == 'a:b:c'
join([1, 2, 3, 4], ' + ') == '1 + 2 + 3 + 4'

trim(string)

Удаляет пробельные символы с начала и конца строки.

trim(' test ') == 'test'

trimStart(string)

Удаляет пробельные символы с начала.

trimStart(' test ') == 'test '

trimEnd(string)

Удаляет пробельные символы с конца строки.

trimEnd(' test ') == ' test'

split(string, separator)

Разделяет строку на подстроки с помощью разделителя.

split('1, 2, 3', ', ') == ['1', '2', '3']
split('Some words', ' ') == ['Some', 'words']

replace(string, find, replacement)

Осуществляет замену первой найденной подстроки на заданную.

replace('Hello, world!', 'world', 'test') == 'Hello, test!'
replace('one, one', 'one', 'two') == 'two, one'

replaceAll(string, find, replacement)

Осуществляет замену всех найденных подстрок на заданную.

replaceAll('Hello, world!', 'o', 'O') == 'HellO, wOrld!'
replaceAll('one, one', 'one', 'two') == 'two, two'

lower(string)

Преобразовывает строку в строчные буквы (нижний регистр).

lower('Hello, World!') == 'hello, world!'

upper(string)

Преобразовывает строку в заглавные буквы (верхний регистр).

upper('Hello, World!') == 'HELLO, WORLD!'

Массивы (списки)

count(array)

Возвращает количество элементов в массиве.

count([1, 2, 3]) == 3

range(start, end, step?)

Генерирует массив из чисел заданной длины.

range(0, 5) == [0, 1, 2, 3, 4]
range(0, 5, 2) == [0, 2, 4]
range(0, -20, -5) == [0, -5, -10, -15]

map(array, expression)

Выполняет преобразование каждого элемента массива. Внутри expression доступна переменная item со значением элемента массива.

map([1, 2, 3], item * 2) == [2, 4, 6]

merge(array1, array2, …)

Объединяет несколько массивов в один.

merge([2, 4], [6, 8]) == [2, 4, 6, 8]

reduce(array, expression, initialValue)

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

reduce(['a', 'b', 'c'], value & '-' & item, '') == 'a-b-c'

reverse(array)

Возвращает массив с элементами, переставленными в обратном порядке.

reverse([1, 2, 3]) == [3, 2, 1]

filter(array, expression)

Удаляет элементы массива, не соответствующие условию. Внутри expression доступна переменная item со значением элемента массива.

filter([-5, 3, -22, 34], item > 0) == [3, 34]

sort(array, expression?)

Сортирует элементы массива в порядке возрастания.

sort([4, 6, -3, 22]) == [-3, 4, 6, 22]

С помощью аргумента expression можно задать критерий, по которому осуществляется сортировка. Внутри доступна переменная item со значением элемента массива.

sort(
	[{x: 5, y: 10}, {x: 10, y: 5}],
	item.y
) == [{x: 10, y: 5}, {x: 5, y: 10}]

slice(array, start, count?)

Обрезает массив, возвращая часть исходного.

slice([1, 2, 3, 4], 1) == [2, 3, 4]
slice([1, 2, 3, 4], 3) == [4]
slice([1, 2, 3, 4], 1, 1) == [2]
slice([1, 2, 3, 4], 1, 2) == [2, 3]

Отрицательный count позволяет делать срез с конца массива.

slice([1, 2, 3, 4], -1) == [4]
slice([1, 2, 3, 4], -3) == [2, 3, 4]
slice([1, 2, 3, 4], -3, 1) == [2]
slice([1, 2, 3, 4], -3, 2) == [2, 3]

unique(array)

Оставляет только уникальные значения массива.

unique([1, 2, 1, 3]) == [1, 2, 3]

indexOf(array, expression)

Возвращает номер элемента в массиве.

indexOf([1, 2, 3], 2) == 1
indexOf([1, 2, 3], 20) == -1

Объекты

arrayToObject(array)

Преобразует массив в объект. Массив должен содержать объекты со значениями "k" (ключ) и "v" (значение):

arrayToObject([
  { "k": "item", "v": "abc123" },
  { "k": "qty", "v": 25 }
]) == {
  "item": "abc123",
  "qty": 25
}

Ключ ("k") обязательно должен быть строкой.

Также вместо набора объектов можно передать набор массивов из двух значений:

arrayToObject([
  [ "item", "abc123" ],
  [ "qty", 25 ]
]) == {
  "item": "abc123",
  "qty": 25
}

objectToArray(object)

Преобразует объект в массив.

objectToArray({
  "item": "abc123",
  "qty": 25
}) == [
  { "k": "item", "v": "abc123" },
  { "k": "qty", "v": 25 }
]

Дата и время

now()

Текущая дата и время.

now() == Date(2022-06-24T14:47:33.000Z)

dateAdd(date, unit, amount)

Прибавляет к дате указанное количество единиц. Допустимые единицы: year, quarter, month, week, day, hour, minute, second, millisecond.

dateAdd(now(), "year", 1) // Прибавить 1 год
dateAdd(now(), "quarter", 1) // Прибавить 1 квартал (3 месяца)
dateAdd(now(), "month", 1) // Прибавить 1 месяц
dateAdd(now(), "week", 1) // Прибавить 1 неделю (7 дней)
dateAdd(now(), "day", 1) // Прибавить 1 день
dateAdd(now(), "hour", 1) // Прибавить 1 час
dateAdd(now(), "minute", 1) // Прибавить 1 минуту
dateAdd(now(), "second", 1) // Прибавить 1 секунду
dateAdd(now(), "millisecond", 1) // Прибавить 1 миллисекунду

Месяцем считается период времени до того же числа (и времени) следующего месяца. Если мы к 31 числу прибавляем месяц, а в следующем месяце отсутствует 31 число, то результатом будет 30 число, то есть последний день месяца.

Примеры:

dateAdd(#2022-01-15 14:05#, "month", 1) == #2022-02-15 14:05#
dateAdd(#2022-08-31 14:05#, "month", 1) == #2022-09-30 14:05#
dateAdd(#2022-01-30 14:05#, "month", 1) == #2022-02-28 14:05#
dateAdd(#2022-01-31 14:05#, "month", 1) == #2022-02-28 14:05#

dateSubtract(date, unit, amount)

Вычитает из даты указанное количество единиц. Все то же самое, что в dateAdd, только в вычитании.

См. также toDate.

Логические функции

if(condition, then, else)

Условный оператор.

if(12 > 4, 'more', 'less') == 'more'
if(12 > 54, 'more', 'less') == 'less'

Поддерживаются множественные условия.

if(condition, then, condition2, then2, …, else)

if(
	price < 100000, "До 100 000 р/мес",
	price < 200000, "От 100 000 до 200 000 р/мес",
	"От 200 000 р/мес"
)

not(value)

Возвращает true вместо false и наоборот.

not(true) == false
not(false) == true

let(variables, expression)

Присваивает имена результатам вычислений.

let({
	var1: 2 + 2 * 2,
}, var1 + var1) == 16

Типы данных

type(value)

Возвращает тип значения: number, string, boolean, array, object, date или null.

type(123) == 'number'
type('hello') == 'string'
type(true) == 'boolean'
type(#2012-12-23#) == 'date'
type([11, 22]) == 'array'
type({ x: 1, y: 2 }) == 'object'
type(null) == 'null'

toBoolean(value)

Преобразует значение в тип boolean (true или false).

toBoolean(0) == false
toBoolean(1) == true
toBoolean(22) == true

toBoolean('') == false
toBoolean('abc') == true

toBoolean(null) == false

Любые массивы, объекты и даты приводятся к true.

toBoolean([]) == true
toBoolean([0]) == true
toBoolean(now()) == true
toBoolean({}) == true
toBoolean({ "x": 0 }) == true

toNumber(value)

Преобразует значение в число.

toNumber('0') == 0
toNumber('123') == 123
toNumber('123.4') == 123.4

toNumber(null) == 0

Если передать в функцию дату, результатом будет метка времени (количество миллисекунд с 1 января 1970 года).

toNumber(now()) == 1656083246000

Массивы и объекты к числу не приводятся, возвращают ошибку.

toString(value)

Преобразует значение в строку.

toString(1) == '1'
toString(123.4) == '123.4'
toString(null) == ''

Если передать в функцию дату, результатом будет метка времени в формате ISO 8601.

toString(now()) == '2022-06-24T15:11:17.000Z'

toDate(value)

Преобразует значение в дату.

toDate(0) == Date(1970-01-01T00:00:00.000Z)
toDate(1656083935000) == Date(2022-06-24T15:18:55.000Z)
toDate(null) == null

Функция понимает разные варианты формата даты ISO 8601:

toDate('2022-06-24T15:18:55.000Z') == Date(2022-06-24T15:18:55.000Z)
toDate('2022-06-24 15:18') == Date(2022-06-24T15:18:00.000Z)
toDate('2022-06-24') == Date(2022-06-24T00:00:00.000Z)
toDate('2022-06') == Date(2022-06-01T00:00:00.000Z)

toDate('2022-06-24T15:18+02:00') == Date(2022-06-24T13:18:00.000Z)
toDate('2022-06-24 15:18 +02:00') == Date(2022-06-24T13:18:00.000Z)

exists(value)

Проверяет существование значения. Проверяет существование переменных, значений объектов и массивов.

exists(a)
exists(a.b)
exists(a[2])