В Caché есть несколько различных способов пройтись по коллекции и выполнить какие-нибудь действия с ее элементами. Самым простым является while-цикл. Такой способ позволяет решить поставленную задачу в императивном стиле. Разработчику приходиться явно заботиться об итераторе, о переходе к следующему элементу и о проверке выхода за пределы коллекции.
Но разве это то, о чем должен заботиться разработчик?! Разработчик должен решать поставленную перед ним задачу, за максимально короткое время с максимально хорошим качеством кода. Было бы очень здорово просто взять коллекцию и применить к ней функцию, которая выполняет необходимые действия на каждом элементе этой коллекции. Не проверять границ, не создавать итератор, не вызывать вручную функцию на каждом элементе. Такой способ решения задач называется декларативным программированием.
Давайте подумаем, как же решить поставленную задачу декларативно, используя средства и возможности Caché.
В языках, поддерживающих функции высшего порядка (например, JavaScript), можно описать функцию обработки элемента коллекции, которую передать в качестве параметра в другую функцию, чтобы применить переданную функцию на каждом элементе.
К сожалению, Caché, не поддерживает функции высшего порядка, которые лаконично позволили бы решить поставленную задачу. Но давайте подумаем, как мы можем стандартными средствами Caché попробовать реализовать эту концепцию.
Для начала, давайте посмотрим на примитивную реализацию задачи, в которой требуется пройти по всей коллекции и вывести каждый элемент.
Для начала, давайте вспомним, о том, что Caché ObjectScript поддерживает парадигму объектно-ориентированного программирования. А раз так, то нужно вспомнить стандартные паттерны проектирования, и попробовать применить их для реализации нашей задачи. Необходимо пройтись по всей коллекции и выполнить какое-то действие с каждым элементом коллекции. Это наводит на мысли о паттерне Visitor.
Давайте определим класс fp.Function, с одним абстрактным методом execute.
Теперь определим реализацию этого «интерфейса» — класс fp.PrintlnFunction.
Окей, немного исправим наш первоначальный код.
Теперь инкапсулируем алгоритм обхода коллекции. Сделаем класс IterableStream.
Теперь решение задачи может быть записано в следующем виде:
Можно теперь инкапсулировать алгоритм создания обертки while-цикла. Для этого создадим класс Streams.
Тогда можно переписать решение задачи в следующем виде:
Итого, задача решена декларативно. Да, появились новые классы. Да, кода стало больше. Но надо отметить, что итоговый код получился лаконичным и более прозрачным. Он не отвлекает нас на детали, а фокусирует внимание на цели решаемой задачи.
Если представить, что в списке стандартных классов Caché были бы классы типа Function, Streams, IterableStream, то оставалось бы лишь создать класс PrintlnFunction.
Ну вот я и поделился с уважаемой аудиторией своими мыслями касательно декларативного программирования в Caché. Всем счастливой разработки!
Но разве это то, о чем должен заботиться разработчик?! Разработчик должен решать поставленную перед ним задачу, за максимально короткое время с максимально хорошим качеством кода. Было бы очень здорово просто взять коллекцию и применить к ней функцию, которая выполняет необходимые действия на каждом элементе этой коллекции. Не проверять границ, не создавать итератор, не вызывать вручную функцию на каждом элементе. Такой способ решения задач называется декларативным программированием.
Declarative programming is when you write your code in such a way that it describes what you want to do, and not how you want to do it.(c) 1800-information
Давайте подумаем, как же решить поставленную задачу декларативно, используя средства и возможности Caché.
В языках, поддерживающих функции высшего порядка (например, JavaScript), можно описать функцию обработки элемента коллекции, которую передать в качестве параметра в другую функцию, чтобы применить переданную функцию на каждом элементе.
[2, 3, 5, 7, 11, 13, 17].forEach(function(i) {
console.log(i);
});
В данном случае создается анонимная функция, которая выводит элемент на консоль. Эта функция передается в качестве аргумента другой функции — forEach.К сожалению, Caché, не поддерживает функции высшего порядка, которые лаконично позволили бы решить поставленную задачу. Но давайте подумаем, как мы можем стандартными средствами Caché попробовать реализовать эту концепцию.
Для начала, давайте посмотрим на примитивную реализацию задачи, в которой требуется пройти по всей коллекции и вывести каждый элемент.
set i = collection.Next("")
while (i '= "") {
set item = collection.GetAt(i)
w item,!
set i = collection.Next(i)
}
Для начала, давайте вспомним, о том, что Caché ObjectScript поддерживает парадигму объектно-ориентированного программирования. А раз так, то нужно вспомнить стандартные паттерны проектирования, и попробовать применить их для реализации нашей задачи. Необходимо пройтись по всей коллекции и выполнить какое-то действие с каждым элементом коллекции. Это наводит на мысли о паттерне Visitor.
Давайте определим класс fp.Function, с одним абстрактным методом execute.
Class fp.Function [ Abstract ] {
Method execute(item As %Numeric) [ Abstract ] {}
}
Теперь определим реализацию этого «интерфейса» — класс fp.PrintlnFunction.
Class fp.PrintlnFunction Extends (fp.Function, %RegisteredObject) {
Method execute(item As %Numeric) {
w item,!
}
}
Окей, немного исправим наш первоначальный код.
set function = ##class(fp.PrintlnFunction).%New()
set i = list.Next("")
while (i '= "") {
set item = list.GetAt(i)
do function.execute(item)
set i = list.Next(i)
}
Теперь инкапсулируем алгоритм обхода коллекции. Сделаем класс IterableStream.
Class fp.IterableStream Extends %RegisteredObject {
Property iterator As %Collection.AbstractIterator [ Private ];
Method %OnNew(iterator As %Collection.AbstractIterator) As %Status [ Private, ServerOnly = 1 ] {
set ..iterator = iterator
return $$$OK
}
Method forEach(function As Function) {
set i = ..iterator.Next("")
while (i '= "") {
set item = ..iterator.GetAt(i)
do function.execute(item)
set i = ..iterator.Next(i)
}
}
}
Теперь решение задачи может быть записано в следующем виде:
do ##class(IterableStream).%New(list).forEach(##class(PrintlnFunction).%New())
Можно теперь инкапсулировать алгоритм создания обертки while-цикла. Для этого создадим класс Streams.
Class fp.Streams {
ClassMethod on(iterator As %Collection.AbstractIterator) As IterableStream {
return ##class(IterableStream).%New(iterator)
}
}
Тогда можно переписать решение задачи в следующем виде:
do ##class(Streams).on(list).forEach(##class(PrintlnFunction).%New())
Итого, задача решена декларативно. Да, появились новые классы. Да, кода стало больше. Но надо отметить, что итоговый код получился лаконичным и более прозрачным. Он не отвлекает нас на детали, а фокусирует внимание на цели решаемой задачи.
Если представить, что в списке стандартных классов Caché были бы классы типа Function, Streams, IterableStream, то оставалось бы лишь создать класс PrintlnFunction.
Ну вот я и поделился с уважаемой аудиторией своими мыслями касательно декларативного программирования в Caché. Всем счастливой разработки!