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

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

Встроенный .freeze() метод

Ballerina предоставляет возможность пометить значение как неизменное, введя встроенный метод .freeze() для anydata типизированных значений (например, array, tuple, map, record, json, xml, table).

Если попытка заморозки успешна, значение помечается как неизменное.

Любые попытки обновить значение с этого момента приведут к панике!

e.g.,

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

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

Встроенный .isFrozen() метод

Доступен boolean встроенный .isFrozen() метод, позволяющий проверить, является ли значение неизменяемым.

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

Официальный Балерина на примере неизменных ценностей объясняет / демонстрирует базовое использование встроенных методов .freeze() и .isFrozen().

Давайте посмотрим на несколько продвинутый пример, основанный на типе потока Балерины.

Потоки в Ballerina - это ограниченные типы, которые позволяют

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

Например:

Допустим, у нас есть поток, ограниченный типом int:

stream<int> intStream = new;

Можно подписаться на этот поток, передав указатель функции, который принимает int в качестве единственного параметра.

intStream.subscribe(intFunction);

где intFunction - функция, которая принимает int в качестве единственного параметра.

function intFunction(int i) {
  ...
}

В этот поток можно было опубликовать int значений, используя функцию stream.publish.

intStream.publish(1);

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

Хотя для простых потоков с ограничениями базового типа проблем не возникнет, как насчет потоков с ограничениями структурированного типа?

Скажем, у меня есть поток, ограниченный типом записи.

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

В приведенном выше примере у нас есть две подписки на поток:

  • changeId - просто обновляет значение поля записи eventString на “Updated EventString”, как только запись получена
  • pauseAndPrintId - делает небольшую паузу после получения записи, а затем печатает значение поля eventString

Для демонстрации мы создали запись со значением поля eventString, установленным на “Original EventString”, и опубликовали ее в потоке.

Когда вы запустите этот пример, результат будет следующим:

$ ballerina run --experimental blog_immutable_values_one.bal
[Subs-1] Received Event
[Subs-2] Received Event
[Subs-1] Updated Event
[Subs-2] Received Event 'eventString': Updated EventString

Обратите внимание, как значение поля eventString также изменилось на “Updated EventString” для второй подписки (pauseAndPrintId).

Но что, если я не хочу, чтобы это произошло? Что, если я не хочу, чтобы подписчики обновляли исходное значение?

Неизменяемые значения спешат на помощь!

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

Обратите внимание, как у нас есть:

  • вызывается .freeze() для значения перед публикацией его в потоке (L21)
  • обновил значение в функции changeId, только если оно не заморожено (L31)

Давайте попробуем запустить обновленную версию сейчас:

$ ballerina run --experimental blog_immutable_values_two.bal
[Subs-2] Received Event
[Subs-1] Received Event
[Subs-2] Received Event 'eventString': Original EventString

Обратите внимание, что исходное значение eventString не изменилось сейчас!

Хотя это все круто, но что, если я хочу обновить полученное значение? Это действительно актуальное требование.

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

Надеюсь, вы нашли это полезным! :)