DeclarativeCOS — Декларативное программирование на Cache +15
Цель проекта — обратить внимание сообщества к улучшению внутреннего ядра COS.
Идея проекта — поддержка лаконичного синтаксиса при работе с циклами и коллекциями.
Итак, что же лаконичного я придумал? Добро пожаловать в примеры!
Примеры
Ключевой концепт проекта — декларативный подход. Нужно указать ЧТО нужно использовать и КАК.
Лично мне всегда не хватало простого оператора/команды/заклинания в терминале COS для того, чтобы вывести коллекцию на экран в том виде, в котором тебе это хочется. А теперь есть две приятные штуки: zforeach и $zjoin!
>s words = ##class(%ListOfDataTypes).%New()
>d words.Insert(“Hello”)
>d words.Insert(“World!”)
>zforeach $zbind(words, “io:println”)
Hello
World!
Здесь стоит рассказать подробнее о функции $zbind. Начать следует с того, что COS можно расширять своими командами и функциями, о чем можно подробно написано в соответствующей документации и статье на портале сообщества разработчиков.
Эта функция создает экземпляр класса Binder. Его задача — связать коллекцию и функцию, которую нужно применить к каждому элементу коллекции. В данном случае, используется стандартная функция с именем “io:println” из DeclarativeCOS, которая для заданного значения value выполняет простую команду:
>w value,!
Команда zforeach работает с экземпляром класса Binder, последовательно проходя по коллекции и применяя функцию к каждому ее элементу.
$zjoin — создает строку из коллекции, объединяя ее элементы между которыми добавляется указанный разделитель.
>s numbers = ##class(%ListOfDataTypes).%New()
>d numbers.Insert(“04”)
>d numbers.Insert(“03”)
>d numbers.Inset(“2017”)
>w $zjoin(numbers, “ / ”)
04 / 03 / 2017
$zmap — создает новую коллекцию из элементов исходной коллекции к каждому элементу которой применена указанная функция.
>set numbers = ##class(%ListOfDataTypes).%New()
>do numbers.Insert($random(100))
>do numbers.Insert($random(100))
>do numbers.Insert($random(100))
>write "[" _ $zjoin(numbers, ", ") _ "]"
[82, 12, 27]
>set hexNumbers = $zmap(numbers, "examples:toHex")
>write "[" _ $zjoin(hexNumbers, ", ") _ “]”
[52, C, 1B]
$zfind — находит первый элемент коллекции, на котором указанная функция возвращает $$$YES. Иначе возвращает null-строку.
>set numbers = ##class(%ListOfDataTypes).%New()
>do numbers.Insert($random(100))
>do numbers.Insert($random(100))
>do numbers.Insert($random(100))
>set primeNumber = $zfind(numbers, "examples:isPrime")
>write "[" _ $zjoin(numbers, ", ") _ "]"
[69, 41, 68]
>write "Prime number: " _ $select(primeNumber="":"<not found>", 1:primeNumber)
Prime number: 41
$zfilter — создает новую коллекцию на основе исходной коллекции, но взяв только те элементы, на которых указанная функция возвращает $$$YES. Если таких элементов нет, то возвращает пустую коллекцию.
>set numbers = ##class(%ListOfDataTypes).%New()
>do numbers.Insert($random(100))
>do numbers.Insert($random(100))
>do numbers.Insert($random(100))
>set filteredNumbers = $zfilter(numbers, "examples:isOdd")
>write "[" _ $zjoin(numbers, ", ") _ "]"
[22, 71, 31]
>write "[" _ $zjoin(filteredNumbers, ", ") _ "]"
[71, 31]
$zexists — проверяет, что в коллекции есть хотя бы один элемент, на котором указанная функция возвращает $$$YES.
>set numbers = ##class(%ListOfDataTypes).%New()
>do numbers.Insert($random(100))
>do numbers.Insert($random(100))
>do numbers.Insert($random(100))
>set hasEvenNumbers = $zexists(numbers, "examples:isEven")
>write "[" _ $zjoin(numbers, ", ") _ "]"
[51, 56, 53]
>write "Collection has" _ $case(hasEvenNumbers, 1:" ", 0:" no ") _ "even numbers"
Collection has even numbers
$zcount — подсчитать количество элементов в коллекции, на которых указанная функция возвращает $$$YES.
>set numbers = ##class(%ListOfDataTypes).%New()
>do numbers.Insert($random(1000))
>do numbers.Insert($random(1000))
>do numbers.Insert($random(1000))
>set palindromicNumbersCount = $zcount(numbers, "examples:isPalindromic")
>write "[" _ $zjoin(numbers, ", ") _ "]"
[715, 202, 898]
>write "Count of palindromic numbers: " _ palindromicNumbersCount
Count of palindromic numbers: 2
Установка
Для установки DeclarativeCOS достаточно скачать с официального GitHub репозитория проекта два файла:
- install.base.xml — просто классы. Без z-функций.
- install.advanced.xml — %ZLANG рутины, которые добавляют z-функции.
Как пользоваться
- Унаследовать класс от DeclarativeCOS.DeclarativeProvider.
- Реализовать метод класса.
- Пометить этот метод аннотацией @Declarative.
- Использовать z-функции DeclarativeCOS.
- Почувствовать счастье.
Подробная инструкция
Class MyPackage.IO extends DeclarativeProvider
{
}
Class MyPackage.IO extends DeclarativeProvider
{
ClassMethod println(value As %String)
{
w value,!
}
}
Class MyPackage.IO extends DeclarativeProvider
{
/// @Declarative("myIO:myPrintln")
ClassMethod println(value As %String)
{
w value,!
}
}
>s words = ##class(%Library.ListOfDataTypes).%New()
>d words.Insert("Welcome")
>d words.Insert("to")
>d words.Insert("DeclarativeCOS!")
>zforeach $zbind(words, "myIO:println")
Welcome
to
DeclarativeCOS!
Как это работает
Проект DeclarativeCOS использует глобал ^DeclarativeCOS для того чтобы сохранить информацию о методах, помеченных аннотацией @Declarative(declarativeName).
Каждая такой метод сохраняется в глобал в следующем виде:
set ^DeclarativeCOS(declarativeName) = $lb(className, classMethod)
Например, для функции io:println:
set ^DeclarativeCOS(“io:println”) = $lb(“DeclarativeCOS.IO”, “println”)
Каждый раз, когда используется функция io:println происходит поиск по глобалу, а потом функция $classmethod сделает вызов исходного метода (DeclarativeCOS.IO # println) на заданном значении.
Заключение
DeclarativeCOS это вклад в новый Cache ObjectScript. В тот самый язык, который действительно помогает своим разработчикам писать программы быстро, лаконично, просто и надежно. Добро пожаловать в критику, поддержку и мнения в комментарии под этим постом!)
Disclaimer: данная статья и мои комментарии к ней является моим мнением и не имеют отношения к официальной позиции корпорации InterSystems.
Комментарии (11)
TheShock
24.04.2017 13:00Простите, а можете объяснить, пожалуйста, в чем декларативность? Мне код кажется очень императивным? «Сделай а, потом б, потом в».
То, что используются процедуры, а не классы еще ведь не делает код декларативнымjxcoder
24.04.2017 13:11Тонкое замечание) спасибо за вопрос!)
Проект помогает скрыть слой циклов. Разработчику остается написать что нужно сделать с элементами коллекции. Всё остальное (по сути, только цикл) проект сделает сам.vics001
24.04.2017 15:24+1Декларативный стиль предполагает «выполни задачу А, но я не знаю как», императивный — «сделай шаг А1, затем шаг А2». Грубо говоря императивный стиль описывает «алгоритм», а декларативный описывает «постановку задачи на формальном языке». If/else/for/while — все это признаки императивного стиля.
jxcoder
24.04.2017 16:46+2Согласен. Но давайте взглянем на этот проект свежим взглядом.
Возьмем достаточно примитивную задачу: сделать строку из коллекции используя заданный разделитель.
Как это сделать в императивном стиле? В императивном стиле мы должны пройти по всей коллекции и вручную соединять ее элементы с разделителем попутно формируя итоговую строку.
Как это будет выглядеть в декларативном стиле? Я себе это понимаю так, что мы говорим языку программирования «Эй, дружище, а сделай мне пожалуйста строку из вон той коллекции! И, да, кстати, вставь пожалуйста между элементами этой коллекции разделитель. Какой? Вот этот.».
Мой проект как раз и идет по второму пути. Приведу небольшой пример. Пусть у нас есть коллекция из букв и мы хотим их собрать в строку. Ну мало ли для каких целей нам это понадобилось. Прошу сконцентрироваться не на самой постановке задачи, а именно на пути ее решения.
Вначале создадим коллекцию из букв.
>s symbols = ##class(%ListOfDataTypes).%New() >d symbols.Insert(“H”) >d symbols.Insert(“e”) >d symbols.Insert(“l”) >d symbols.Insert(“l”) >d symbols.Insert(“o”)
А теперь решим поставленную задачу.
>w $zjoin(numbers, “ ”) H e l l o
Всего одна функция. Никакой императивности. Мы говорим что хотим и получаем это.TheShock
24.04.2017 17:10Разве воспользоваться библиотечной процедурой — уже декларативность? Си — декларативен?
А ваше создание коллекции из букв разве не императивность?
По-моему, вы написали обычную процедурную либу.
Сейчас почему-то модно называть стиль php 4-й версии декларативным и функциональным)jxcoder
24.04.2017 17:26+1:) Создание коллекции это отдельная тема, которая в моем проекте не затрагивается. Проект работает с уже созданными коллекциями. Во всяком случае пока)
«Библиотечная процедура» — звучит очень здорово!) я понимаю к чему Вы ведете.
Я понимаю так, что «декларативность» — способ решения задачи. Если мы решаем какую-то задачу и при этом не заморачиваемся над тем, чтобы описать ее реализацию, то пожалуй это всегда здорово независимо от того как мы это будем называть) Ведь так?)
Особенно чудесно, когда язык сам дает инструменты описать решение задачи. По сути всё можно свести к вызову некоторых функций. Просто в каждом конкретном случае уровней абстракций больше или меньше.
m03r
Простите, пожалуйста, но про какой язык нельзя сказать, что он «действительно помогает своим разработчикам писать программы быстро, лаконично, просто и надежно»?
P. S. А где монады?
alexkunin
Brainfuck же.
А если серьезно, примеров можно много привести: AppleScript, ассемблер, все языки, созданные во времена жестких ограничений по ресурсам.
m03r
Ну, всё же они (кроме Brainfuck) были созданы именно для помощи программистам — насколько это было возможно при имеющихся ресурсах.
alexkunin
Ну, да, вы правы — если «быстро, лаконично, просто и надежно» относительно машкодов.
Тоже верно, но с какого-то момента программистов стало много, все разного уровня, и поэтому Бейсик с С++ сравнивать нельзя, т.к. у целевых аудиторий очень разные понятия о «просто», «лаконично» и т.д.В общем я с вами даже не спорю, просто поправил бы ваше «про какой язык нельзя сказать» на «про какой профессиональный язык нельзя сказать». Просто прицепился к максимализму во фразе, не обращайте внимания.
jxcoder
Монад пока нет, при желании можно добавить.