А.И.Орехов
© 2006 г.
Статья о проекте нового языка программирования высокого уровня.
В Интернете кипят споры. Что лучше? Си или Delphi, Java или Ruby. Противопоставления самих языков и концепций, лежащих в их основе. Некая "псевдорелигиозность" присуща как начинающим программистам, так и светилам кибернетики. Доступно множество серьезных аналитических статей, где доказывается не состоятельность и преимущества тех или иных подходов.
Тем временем все больше новых языков сочетает разные парадигмы. Ведь что-то лучше программировать в функциональном стиле, а другие задачи предпочтительно решать процедурно ООП. Где то важна гибкость, а где то быстродействие. Какое место займет новый язык программирования.
Разработка предназначается для создания инженерных и научных программ, не требующих высокого быстродействия. Развитые средства функционального программирования, которые действуют подобно ООП. И четкая иерархическая структура, позволяющая легко читать исходник. Предпосылкой создания является тот факт, что мало современных разработок в области инженерных задач, которым полезна выразительность и краткость записи, но не требуется высокая вычислительная мощность. Скорость написания программы, ее лаконичность, мощные семантич. возможности приоритетны. Сейчас идет разработка языка, подробная информация на сайте www.logica.net.ru. Выход альфа версии транслятора под .NET предварительно намечен к осени 2007 года.
Иерархическая структура
"Логика" основана на подходах процедурного программирования и ООП, ФП и элементах ЛП. Но они не смешиваются друг с другом,
а находятся в иерархической взаимосвязи. Условно можно выделить три слоя языка:
1 слой: ООП и процедурное программирование, классы и объекты;
2 слой: функциональное программирование и объекты-значения;
3 слой: логическое программирование в ограничениях, запросы
Из методов (слой 1) доступны все структуры языка,
можно вызывать любые функции, запросы и т.п.. В функциях (слой 2)
можно применять только объекты ФП и ЛП. Из запросов (слой 3) можно
вызывать только конструкции логического программирования. Т.е.
из 1го уровня можно вызывать все что угодно. Из 2го уровня доступен
только 2ой и 3ий уровень. Из 3го уровня - только 3ий слой языка.
Благодаря такой структуре легко разобраться в исходнике - где какие подходы действуют и применены. Нет смеси, которая может
затруднить чтение и понимание программы.
Многие алгоритмы компактнее, лучше для восприятия в функциональном стиле. Но некоторые задачи (например ввод/вывод) невозможно
решить в рамках этой концепции. Поэтому в языке реализованы разные подходы, программист сам выбирает где определить функцию,
а где объект с методами и состоянием.
Синтаксис
class - класс, определение императивного объекта
proc - процедура, метод класса
proc static - статич. метод класса (может вызываться без создания экземпляра)
func - определение функции, аппликативного объекта
value - определение значения
query - запрос, логическое программирование
Значащие отступы (через табуляцию), аналогично Python. Но допустимы только символы табуляции, не пробелы.
Переменных нет в понимании Си или Паскаль. Есть имена, связываемые с объектом. У имен нет типов.
Продолжение строки '_'. Длинные строки можно разбивать на неск. физических. При этом пробелы и табуляция игнорируется.
Структура файла и компиляция
Программа на Логике состоит из файлов с расширением *.logica. Это не модули. Транслятор объединяет все файлы в один
и компилирует его. Т.е. глобальное пространство имен у всех файлов общее. Поскольку язык динамический, лучше разбивать сложный
проект на несколько независимо компилируемых сборок. А не делать сложные конструкции с пространствами имен в одном модуле, как
позволяет C#. "Логика" ориентирована на небольшие быстро компилируемые сборки, которые можно подключать к основной программе.
Каждый отдельный файл может содержать заговолок и определения объектов:
классы (class), функции (func), значения (value) и запросы (query).
Сначала идет секция import, в ней
перечисляются используемые сборки-библиотеки. Затем секция constante. В Логике нет глобальных объектов и переменных,
но есть глобальные константы.
import
System
System.Console
constante
pi = 3.1415
str_test = 'тест'
Процедуры содержатся только в классах, свободных процедур нет. В программе должен быть класс App с определенным
методом int Main(list string args). С вызова этого метода начинается выполнение программы.
class App
proc static int Main(list string args)
System.Console.WriteLine('Добрый день :-)')
0
Переменные
Переменные в функциональном и логическом программировании близки к соответствующему математич. понятию.
Символическое имя объекта.
Это отличается от процедурной модели программирования, где переменная - некая ячейка памяти. На Си или Паскале можно записать
такой код:
int a = 3;
a = 5;
a = 7;
В ФП и декларативном программировании это не возможно по смыслу.
x = 3
x = 7 Ошибка! 3 = 7
Имя 'x' связано с объектом число 3. Теперь x это обозначение для 3,
синоним. Логически не верно сравнивать его с другим объектом. Это
называется неизменяемость. Подход способствует более стройному
программированию и уменьшает кол-во
ошибок. Но часто требуются именно ячейки памяти, для задач
ввода/вывода. Либо просто по смыслу алгоритм более эффективен с
ячейками.
В Логике можно создавать такие переменными через ключевое слово var. Их можно использовать в классах и процедурах.
class MyClass
var float x
proc test()
x = 5
x = x + 10
x = 7
var float y
y = 2*x
y = 0
Списки
Основная встроенная структура данных - список.
Элементами могут быть любые значения, объекты. Оптимизирован доступ по
индексу. Сама структура декларативна, все составляющие должны быть
заданы в момент создания списка. После этого длину уже невозможно
изменить. Создание списка: [ ]
lst_a = [3, 3.14, "Добрый день"]
r1 = lst_a[0]
r2 = lst_a[2]
Список можно использовать в процедурах как массив, если в нем элементы var.
a = [var 3, var 7, var 'hi']
b = a[2]
a[2] = 'test'
a[2] = 3.14
Для описания фильтров со списками введено ключевое слово list.
list
list int
list bool|string
[float, int, int] # дополнительная форма фильтра без list
Декларативность
Структура данных декларативна. Но элементами списка могут быть недекларативные объекты: экземпляры class и переменные var. Это важно для разделения императивного программирования (процедуры) и функционального (func). Если список содержит только декл. элементы, то его можно свободно передавать в аргументах func. В противном случае данный список можно будет использовать только в процедурах.
Проверить объект можно с помощью встроенных функций:
bool imperative(object) - истина, если объект недекларативен
bool applicative(object) - истина, если объект декл. ( applicative(x) = not imperative(x) )
Функции в Логике
Функции в языке несут некоторые черты объектно-ориентированного программирования.
Подход: Функция это неупорядоченный список выражений.
func myfunc1(a, b)
t = 2*k + s
#где:
k = 10*a + 3.14
s = 2*b
t/2
Функция состоит из именованных выражений. Порядок не важен, транслятор сам анализирует как выполнять код.
Последнее выражение без имени - результат.
func myfunc2(a, b): myfunc1
k1 = 4 * 3.14
s = 4*b + k1
Наследование функций. Мы описали новую функцию func2, которая наследуется от func1. Изменили поведение функции,
переопределив (перегрузка) выражение s и добавили новое k1.
func myfunc1(a, b)
t = 2*k + s
#где:
k = 10*a + 3.14
s = 2*b
t/2
for operations
func add(myfunc1 j):
...
...
В секции for operations можно указать ассоциированные обработчики для определенных операций (сложить, вычесть, умножить и т.п.).
В Логике принят оригинальный подход, который привносит некоторые
особенности ООП в ФП. Исходный текст получается короче и лучше для
восприятия, чем ООП с классами и объектами.
Многие функциональные языки позволяют манипулировать функциями
"снаружи", без доступа к коду функции. В Логике вы получаете удобные
средства работать с внутренней структурой "изнутри".
Частично выполняемые функции
Логика поддерживает развитые средства частичного выполнения функций. Функция есть совокупность выражений.
Аргументы можно задавать в любом количестве и сочетании. Те выражения, для которых достаточно информации выполнятся. Остальные
будут ждать своих аргументов. Вычисления производятся в момент создания объекта-функции, после этого состояние не изменяется.
Что вернет функция. Если заданы все аргументы и данных достаточно, то функция вычислит результат. Если же
аргументов не достаточно, то будет создан новый объект - частично выполненная функция в качестве результата. Функция есть объект
первого порядка, поэтому может быть полноценным возвращаемым значением наравне с числами, строками и т.п..
Существует два способа указывать аргументы: позиционно, без указания имен; и именовано, не зависимо от позиции.
func float f(float a, float b, float c)
k = 3*a + 1
s = 8*b
(k + s) * c
f(2, 5.5, 7) # аргументы заданы, результат
f1 = f(2, 5.5) # частичное выполнение
f1(7) # новый объект-функция
f2 = f(,, 5.5)
f3 = f(2, , 7) # аргументы в любых сочетаниях
# именованные аргументы
f(b = 5.5)
# смешанный стиль, позиционные вначале
f(2, c=7)
f(, 5.5, c=7)
Механизм частичного выполнения мощный инструмент, позволяющий сделать исходник компактнее и эффективнее в ряде
случаев.
#( Функция сплайн по эксперимент. снятым точкам
xy - список эксперимент. снятых точек;
xmin, xmax - границы диапазона;
x - нужное значение для вычисления y
)#
func spline1(list point xy, xmin, xmax, x)
# Сначала надо вычислить коэффициенты сплайн и
# Создать новый список точек, равномерный по всему
# диапазону
...
xy_norm = ...
# Теперь можно выполнить запрос на значение x:
y = некая сплайн функция от x по вычисленным коэф. и_
данным xy_norm
y
for operations
func spline1(x) add(spline1(x) j)
#( находим пересечение диапазонов xmin и xmax
xmin_new, xmax_new;
вычисляем новый список xy_norm_new
и нужные данные
)#
...
xmin_new, xmax_new = ...
xy_norm_new = ...
...
spline1(-, xmin_new, xmax_new, , xy_norm = xy_norm_new)
В инженерной и научной деятельности часто бывает нужно обрабатывать экспериментально снятые координаты графиков.
Можно создать удобный объект-функцию, которая получит на вход произвольный список точек (x,y) и найдет необходимые промежуточные
значения при помощи аппроксимации или сплайн. Наша функция создает внутреннюю структуру, равномерный список координат.
Экспериментальные данные могут быть произвольными, неравномерными.
(к примеру [x, y] = [-10, 5], [-5, 4], [-3.5, 3.8], [0, 9.3], [4, 7], [10, 3.1]) Объект функция должна произвести вычисления
и построить новый список, равномерный по оси x ([-10, -9, -8...]) с нужным шагом. После этого легко получить значение в любом
месте по сплайн или др. формуле.
f = spline1(data, -10, 10) # получили нашу функцию с аппроксимацией
a = f(7.5) # может применять как обычную функцию в любом месте программы
b = f(0)
Также бывает нужно, сочетать несколько экспериментальных функций. К примеру акустический прибор состоит из
микрофона (график характеристики), усилителя и фильтров, телефона (график характеристик). Графики микрофона и телефона
снимаем экспериментально, усилитель моделируем математически. Нам надо "сложить" эти графики. Обработчик add легко
решает эту задачу.
fun_mk = spline1(datamk, 0, 8000)
fun_tel = spline1(datatel, 0, 8000)
fun_res = fun_mk + fun_tel
...
res = fun_res(x)
Развитые методы функционального программирования позволяют легко и компактно решать многие инженерные задачи.
Обратим внимание на эффективность. Реализации языков через замыкания (карринг) имитируют частич. вып. внешне, синтаксически.
Но реально накапливают аргументы и потом полностью выполняют функцию. Представьте, что функции акустического прибора записаны
в цикле по частоте (Гц). Реализация через замыкания работала бы очень медленно, поскольку все функции повторяли
бы вычисления для каждой итерации, в том числе полный расчет выровненного массива и коэффициентов сплайна. В Логике же будут
производиться только необходимые вычисления. Функции fun_mk и fun_tel создадут данные сплайна и массив заранее. В цикле же
будет вызываться fun_res - оптимизированная, без лишней работы.
|