Ярославский Государственный Университет им. П.Г. Демидова
Опубликован: 06.11.2008 | Доступ: свободный | Студентов: 987 / 62 | Оценка: 4.50 / 4.00 | Длительность: 10:47:00
Лекция 12:

Модель Бэкуса алгебры программ: фундаментальные формы и определение функций

< Лекция 11 || Лекция 12: 12 || Лекция 13 >

Пример 3. Функцию long длины кортежа мы ввели как примитивную. Покажем, как ее можно создать с помощью конструкций языка модели Бэкуса. Заметим, что для определения длины кортежа нам нужно ввести счетчик элементов кортежа, который бы увеличивался, когда мы считаем эти элементы. Необходимо поэтому задание начальной длины такого счетчика. Поэтому введем 2 функции: init для задания счетчика с его нулевым начальным значением и count для подсчета числа элементов. Тогда необходимую функцию можно определить как композицию этих функций:

DEF long :: count*init.

Перейдем теперь к построению функции count. Для этого заметим, что нам нужно иметь одновременно и кортеж с текущим количеством еще не подсчитанных элементов, и счетчик уже подсчитанных элементов. Поэтому текущими данными этой функции должен служить вспомогательный кортеж с двумя элементами:

  1. кортеж с неподсчитанными элементами исходного кортежа;
  2. счетчик подсчитанных элементов исходного кортежа.

Создать такой кортеж с начальным значением элементов (первый элемент - исходный кортеж, а второй - обнуленный счетчик) должна функция init. Для ее описания нужно использовать форму конструкции с двумя функциями (тождественной для копирования исходного кортежа и генератора константы 0):

DEF init :: (id, const(0)).

Теперь определение функции count можно получить, используя следующую рекурсивную идею: при помощи конструкции уменьшаем на единицу число неподсчитанных элементов исходного кортежа (хвост первого элемента вспомогательного кортежа) и увеличиваем на единицу значение счетчика подсчитанных элементов (специализатор увеличения на единицу). Это необходимо делать до тех пор, пока первый элемент вспомогательного кортежа не станет пустым (тогда будут подсчитаны все элементы исходного кортежа) и в этом случае вернуть в качестве окончательного значения второй элемент вспомогательного кортежа. В результате получим следующее описание:

DEF count :: null*1 -> 2; count*(t1*1, (s+1)*2).

Мы получили полное описание функции long. Так, например,

long : <x1, x2, x3> = count:(init:<x1, x2, x3>) =
       = count:< <x1, x2, x3>, 0> = count:"x2, x3>, 1> =
       = count:< <x3>, 2> = count:">, 3> = 3.

Пример 4. Определим теперь функцию транспонирования матрицы trans, которую мы ввели ранее как примитивную. Для этого заметим, что первая строка результата состоит из первых элементов строк исходной матрицы. Поэтому для ее получения нужно применить селектор 1 для всех элементов кортежа исходной матрицы, т. е. взять общую аппликацию этого селектора: A1. Полученную таким образом 1-ю строку результирующей матрицы нужно присоединить слева к результату транспонирования матрицы без первых элементов в строках, которая получается в результате выборки хвостов для каждой строки, т. е. опять общей аппликации, но уже для функции хвостов строк: At1. Поэтому получается следующее решение, использующее рекурсию:

DEF trans :: null -> <>; appendl*(A1, trans*At1).

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

trans :<< 1; 2; 3 >; < 4; 5; 6 >; < 7; 8; 9 >>=
 = appendl :<< 1; 4; 7 >; trans :<< 2; 3 >; < 5; 6 >; < 8; 9 >>>=
 = appendl :<< 1; 4; 7 >;
         appendl :<< 2; 5; 8 >; trans :<< 3 >; < 6 >; < 9 >>>>=
 = appendl :<< 1; 4; 7 >; appendl :<< 2; 5; 8 >;
                            appendl :<< 3; 6; 9 >; trans :<>>>>=
 = appendl :<< 1; 4; 7 >; appendl :<< 2; 5; 8 >;
                            appendl :<< 3; 6; 9 >; trans :<>>>>=
 = appendl :<< 1; 4; 7 >; appendl :<< 2; 5; 8 >;
                                 appendl :<< 3; 6; 9 >; <>>>>=
 = appendl :<< 1; 4; 7 >; appendl :<< 2; 5; 8 >; << 3; 6; 9 >>>>=
 = appendl :<< 1; 4; 7 >; << 2; 5; 8 >; < 3; 6; 9 >>>=
 = appendl :<< 1; 4; 7 >; < 2; 5; 8 >; < 3; 6; 9 >> :

Пример 5. Определим функцию факториала. Обозначим ее как !. Будем исходить из определения

!n=1, если n=0; иначе равен n* !(n-1)

и проведем пошаговую детализацию (сверху вниз).

DEF ! :: p -> f; g;

где p – условие равенства аргумента нулю, fфункция, возвращающая 1, а gфункция умножения аргумента на значение факториала от уменьшенного на 1 значения аргумента. Поэтому

DEF p :: eq * f1,

где функция f1 должна создать кортеж из пары – значение аргумента и 0:

DEF f1 :: (id; const(0)).

Теперь функция p полностью определена. Перейдем теперь к функциям f и g:

DEF f :: const(1),
DEF g :: x * (g1; g2),

где g1 есть id, а g2 определяется как композиция факториала и функции ( s - 1 ), образованной специализатором:

DEF g2 :: ! * (s - 1).

Объединяя все в одну конструкцию определения факториала, получим:

DEF ! :: eq * (id; const(0)) -> const(1); x * (id; ! * (s - 1)):

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

! : 3 = eq :< 3; 0 > -> const(1) : 3; x :< 3; ! : - < 3; 1 >>=
 = x :< 3; ! : 2 >=
 = x :< 3; eq :< 2; 0 >-> const(1) : 2; x :< 2; ! : - < 2; 1 >>=
 = x :< 3; x :< 2; ! : 1 >>=
 = x :< 3; x :< 2; eq :< 1; 0 >-> const(1) : 1;
                                        x :< 1; ! : - < 1; 1 >>>>=
 = x :< 3; x :< 2; x :< 1; ! : 0 >>>=
 = x :< 3; x :< 2; x :< 1; eq :< 0; 0 >-> const(1) : 0;
                                      x :< 0; ! : - < 0; 1 >>>>>=
 = x :< 3; x :< 2; x :< 1; 1 >>>= x :< 3; x :< 2; 1 >>=
 = x :< 3; 2 >= 6:

Пример 6. Скалярное произведение векторов. Исходным данным является кортеж с двумя элементами - векторами, т. е. также кортежами. Иными словами, исходные данные это матрица с двумя строками-векторами. Скалярное произведение - это сумма попарных произведений одноименных компонент векторов. Поэтому надо сначала разбить элементы векторов на пары, и это проще всего сделать, транспонировав исходную матрицу векторов с помощью функции trans - тогда каждая строка транспонированной матрицы и будет соответствующей парой элементов. Затем надо для каждой строки транспонированной матрицы получить произведение, т. е. применить общую аппликацию произведения A x: получится вектор (кортеж) попарных произведений. Наконец, надо просуммировать все элементы полученного вектора, т. е. применить редукцию сложения /+. Таким образом, скалярное произведение можно определить как композицию:

DEF IP :: (/+)*( A x)*trans.

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

IP :<< 1; 2; 3 >; < 4; 5; 6 >>= 
 = /+ : (A x ) : trans :<< 1; 2; 3 >; < 4; 5; 6 >>=
 = /+ : (A x ) :<< 1; 4 >; < 2; 5 >; < 3; 6 >>=
 = /+ :< 4; 10; 18 >= 32:

В заключение отметим особенность стиля программирования на языке модели Бэкуса:

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

Упражнения

  1. Определите функцию revers, переворачивающую в обратном порядке любой кортеж, но не его элементы.
  2. Определите функцию allrevers, переворачивающую в обратном порядке любой кортеж и любые его элементы.
  3. Определите функцию NulVect, обнуляющую все элементы вектора.
  4. Определите функцию nul, обнуляющую все элементы любого объекта.
  5. Определите функцию vector, проверяющую, является ли объект одномерным вектором.
  6. Определите функцию matrix, проверяющую, является ли объект двумерной матрицей.
  7. Определите функцию NulDiag, обнуляющую все элементы диагонали матрицы.
  8. Определите функцию Nul1col, обнуляющую все элементы 1-го столбца матрицы.
  9. Определите функцию Nul1line, обнуляющую все элементы 1-й строки матрицы.
  10. Определите функцию NulExDiag, обнуляющую все элементы матрицы, кроме главной диагонали.
  11. Определите функцию SqMatrix, проверяющую, является ли объект квадратной матрицей.
  12. Определите функцию Turn90SqMatrix, поворачивающую квадратную матрицу на 90 градусов по часовой стрелке.
< Лекция 11 || Лекция 12: 12 || Лекция 13 >