Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Языки программирования. Концепции и принципы

.pdf
Скачиваний:
2
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
☆
Функциональное программирование (модель Б)
обозначает результат применения функции f к объекту X. Например:
+ : <1,2> = 3, 1 : <А,В,С> = А, 2 : <А,В,С> = В, t1 : <А,В,С> = <В,С>,
где слева от знака аппликации «:» выписаны знаки функций сложения, извлече ния первого элемента, извлечения второго элемента и хвоста кортежа соответ ственно.
301
2.2.4. Функции
Все Бфункции отображают объекты в объекты (то есть имеют тип О > О) и со храняют неопределенность (то есть f : <?> = <?> для всех f).
Каждый знак Бфункции – это либо знак примитивной функции, либо знак
формы, либо знак функции, определенной в D. Другими словами, в модели Б раз личаются, с одной стороны, предопределенные функции и формы, а с другой – функции, определяемые программистом с помощью пополнения D.
Равенство f : X = <?> возможно всего в двух случаях, которые полезно разли
чать.
Вопервых, выполнение операции «:» может завершаться и давать в результате
<?>.
Вовторых, оно может оказаться бесконечным – тогда это равенство считается
справедливым уже по определению операции «:». Другими словами, виды ненор мального выполнения аппликации в модели Б «склеиваются», не различаются.
2.2.5. Условные выражения Маккарти
Ниже будем пользоваться модификацией так называемых условных выражений Маккарти. Условное выражение Маккарти – это запись вида
( Р1 > Е1, ... ,Pn > En, Т > Е ),
где через Р с возможными индексами обозначены условия (предикаты, логиче ские функции), а через Е с возможными индексами – выражения, вычисляющие произвольные объекты. При конкретной интерпретации входящих в эту запись переменных ее значение получается по следующему правилу. Последовательно вычисляются условия, начиная с первого, пока не найдется истинное (такое всегда
найдется. Почему?
которого и становится значением всего условного выражения Маккарти.
Аналоги такой конструкции широко используются в ЯП. В сущности, возмож
ность согласования с левой частью конкретного МТпредложения можно рас сматривать как аналог условия Pi, а правую часть МТпредложения – как аналог выражения Ei.
Упражнение. Укажите различия между Pi, Ei и названными их МТаналогами.
Вслед за Джоном Бэкусом будем записывать условные выражения Маккарти
в виде
). Затем вычисляется соответствующее выражение, значение
302
Перспективы языков программирования
Р1 > Е1;…; Рn > En; Е, то есть опуская внешние скобки и последнее тождественно истинное условие,
а также используя точку с запятой в качестве разделителя (запятая занята под знак формы «конструкция»).
2.2.6. Примеры примитивных функций
Некоторые из уже известных вам функций опишем заново, вопервых, чтобы можно было освоиться с введенными обозначениями, и, вовторых, потому, что они играют важную роль в алгебре программ (АП), к описанию и применению которой мы стремимся. Определения некоторых ранее рассмотренных функций уточнены здесь с тем, чтобы упростить АП (соответствующая модификация МТ определений может служить полезным упражнением). Отличия касаются, как правило, тонкостей (действий с пустыми и неопределенными объектами, а также учета внешних скобок в представлении объектов), однако эти тонкости суще ственны с точки зрения АП.
Селекторы (селекторные функции). Будем использовать целые числа для обо значения функций, выбирающих из кортежейобъектов элементы с соответству ющим номером:
1 : X :: X = <Х1,…,Хn> > X1; <?>, то есть функция определяется через аппликацию (этот прием мы уже применяли
в модели МТ). Знак «::» используется, как и раньше, в смысле «есть по определе нию», чтобы отличать определение от обычного равенства. Приведенная запись означает, что применение функции 1 к объекту X дает результат X1 (первый эле мент кортежа), если X – кортеж, иначе – не определено (то есть аппликация завершает вычисление с результатом <?>).
Вообще, для положительного целого s:
s : X :: X = <Х1,…,Xn> & n>=s > Xs; <?>.
Здесь в условном выражении Маккарти употреблено более сложное условие вида
X = <Х1, ... ,Хn> & n > = s.
Замечание. Такого рода определения (и это, и предыдущее) следует рассматривать лишь как достаточно понятное сокращение точного определения, которое может быть дано, например, в модели МТ. Модель МТ неплохо подходит для таких опре делений, потому что в ее базисе имеются мощные средства анализа и синтеза. Ведь нужно сказать следующее: если объект X имеет вид
<Х1,…,Хn> и длина кортежа больше s, то заменить X на sю компоненту кортежа. Если же X не
имеет такого вида («согласование невозможно» с соответствующим МТпредло жением), то выдать <?>. Например:
5{(t1 t2 t3 t4 t5 е)}> t5 . 5{е} > <?> .
Функциональное программирование (модель Б)
Хвост
t1 : X :: X = <Х1> > <>; X = <Х1,…,Хn> & n > = 2 >
<Х2, ... ,Хn>;
<?>. Отличие от МТфункции «хвост» в том, что результат всегда в скобках, если
определен и непуст.
Тождественная функция id : X :: X Логические функции – атом, равенство, пустой атом : X :: (X – это атом) > Т;
X /= <?> > F; <?>. Таким образом, атом:<?> = <?> (сохраняет неопределенность), eq : X :: X = <Y,Z> & Y = Z > Т;
X = <Y,Z> & Y /= Z > F; <?>. Например, eq:<2,2,3> = <?>, так как аргумент не пара, а тройка.
null : X :: X = <> > Т; X /= <?> > F; <?>. Сложение, вычитание, умножение, деление
+ : X :: X = <Y,Z> & числа (Y,Z) > Y+Z; <?>.: X :: аналогично. Mult : X :: аналогично. [Привычная звездочка занята] div : X :: аналогично. [Косая черта занята]
303
Расписать левым, расписать правым distl : X :: X = <Y,<>> > <>;
X = <Y,<Z1,...,Zn>> > <<Y,Z1>,...,<Y,Zn>>; <?>. distr : X :: X = <<>,Y> > <>; X = <<Z1,...,Zn>,Y> > <<Z1,Y>,…,<Zn,Y>>; <?>.
Функция distl присоединяет левый элемент аргумента ко всем элементам кор
тежа, который служит правым элементом аргумента функции, а функция distr, наоборот, присоединяет правый элемент аргумента. Например:
distl:<<A,B>,<C,D>> = <<<A,B>,C>,<<A,B>,D>>. distr:<<A,B>,<C,D>> = <<A,<C,D>>,<B,<C,D>>>.
Транспонирование trans : X ::
X =<<X11,...,X1m>, <<X11,...,Xn1>, <X21,...,X2m>, > <X12,...,Xn2>,
… …
<Xn1,...,Xnm>> <X1m,...,Xnm>>;
<?>.
304
Перспективы языков программирования
Здесь для наглядности строки матрицы выписаны друг под другом. С подоб ной функцией мы работали в модели МТ. К объектам, не являющимся матрица ми, она неприменима (то есть результат – <?>).
Присоединить
appendl : X :: X = <Y,<>> > <Y>;
X = <Y,<Z1,...,Zn>> > <Y,Z1,...,Zn>;
<?>.
Таким образом, эта функция присоединяет левый элемент аргумента в каче стве самого левого элемента правой компоненты аргумента. Поэтому ее есте ственно назвать «присоединить левый». Например:
append1:<<A,B>,<C,D>> = <<A,B>,C,D>.
appendr : X :: X = <<>,Y> > <Y>;
X =<<Y1, ... ,Yn,Z> > <Y1, ... ,Yn,Z>;
<?>.
Такую функцию естественно назвать «присоединить правый».
Например:
appendr : <<A,B>,<C,D>> = <A,B,<C,D>>.
2.2.7. Примеры форм, частично известных по работе в модели МТ
Композиция (обозначение – *)
(f *g) :X::f : (g :X).
Конструкция (обозначение – ,)
(f1, ... ,fn) : X :: <f1:X, ... , fn:X>.
Замечание. В отличие от MTконструкции, здесь результат – целостный объект в скобках, причем все n результатов применения каждой функции – также целост ные объекты (то есть каждая компонента общего результата явно отделена от ос тальных). В MTконструкции отделить результаты функцийкомпонент в общем случае невозможно, так как это могут быть не термы, а выражения, не разделенные между собой явно. По указанной причине для МТконструкции не выполняются некоторые важные алгебраические законы, верные в случае Бконструкции. Так что указанные отличия МТ и Бконструкций существенны.
Упражнение. Определите MTконструкцию со свойствами, аналогичными Бкон струкции.
Подсказка. Нужно использовать не конкатенацию выражений, а функцию, анало гичную созданию списка из атомов и (или) списков.
Вопрос. Какие важные алгебраические законы не выполняются для МТконструкции? Подсказка. Что будет, если некоторый объект присоединить левым, а затем вы
брать первый (то есть левый) элемент? Зависит ли результат от структуры этого объекта в случае Бконструкции? А в случае МТконструкции?
Функциональное программирование (модель Б)
Условие
(р > f;g) : X :: (р:Х) = Т > f:X;
(р:Х) = F > g:X; <?>.
В отличие от условного выражения Маккарти, в форме «условие» все аргумен
ты – функции, а не объекты. Вслед за Бэкусом вместо
(p1 > f1; (р2 > f2;g))
будем писать без скобок:
p1 > f1; р2 > f2; g.
Генератор постоянных
const(X) : Y :: Y – <?> > <?>; X. Аргумент этой формы – некоторый объект, а результат – функция, значение
которой на всех определенных объектах совпадает с объектом, заданным в каче стве аргумента формы. Другими словами, результатом формы служит функция константа с заданным значением. Например, для любого Y /= <?>
const (<A,B>):Y = <А,В>. Редукция (обозначение – /)
/f : X :: Х=<Х1>> X1; X = <Х1,…,Хn>&n >= 2 > f:<Xl,/f:<X2,…,Хn>>; <?>.
305
Общая аппликация (обозначение – А) Af: X :: X = <> > <>; X= <Х1, ... ,Xn> > <f:Xl, ,f:Xn>; <?>.
Отличия от МТобщейаппликации аналогичны отличиям МТ и Бконструкций.
Упражнение. Написать определение МТобщей аппликации, соответствующее
приведенному Бопределению.
Итерация
(while р f) : X ::
(р:Х) =Т > (while р f):(f:X); (р:Х) = F > X; <?>.
Смысл этой формы – в том, что функция f в общем случае многократно приме
няется к объекту X до тех пор, пока не станет ложным результат применения к X логической функции (условию) р.
Специализатор (обозначение – s) (s f X) : Y :: f:<X,Y>.
У этой формы два аргумента: бинарная операция f (функция от двухэлемент
ных объектов) и объект X. Результат формы – унарная операция, получающаяся из f при ее специализации (иногда говорят «конкретизации») за счет отождеств ления первого операнда с объектом X. Например:
306
(s + 1) : Y =+:<1,Y>, то есть 1 + Y в обычных инфиксных обозначениях.
Перспективы языков программирования
2.2.8. Определения
Бопределение новой функции – это выражение вида
DEF I :: r, где в качестве I указано не использованное ранее название функции (функцио
нальный символ), а в качестве r – функциональная форма (которая может зави сеть от I; допустимы и рекурсивные определения). Например:
DEF last :: null * t1 > 1; last * t1, где справа от знака «::» – форма «условие», в которую входят в качестве р компо
зиция null * t1, в качестве f – селекторная функция «1», а в качестве g – компози ция last * t1.
Так что last:<A,B>= В. Убедитесь, что это действительно верно!
Нельзя выбирать названия новых функций так, чтобы они совпадали с уже введенными или предопределенными. Использование в D функционального символа всюду вне левой части определения (то есть в качестве I) формально означает, что вместо него нужно подставить соответствующее r и попытаться вы числить полученную аппликацию. Если в процессе вычисления снова встретится функциональный символ, определенный в D, то снова заменить его соответст вующей правой частью определения и т. д.
Ясно, что можно зациклиться. Как уже было сказано, это один из способов по лучить <?> в качестве результата. Конечно, в разумных рекурсивных определе ниях прямо или косвенно применяются условные выражения Маккарти, которые позволяют вычислить аппликацию за конечное число шагов (за счет чего?).
Бопределение – в сущности, еще одна форма, ставящая в соответствие функ циямаргументам определяемую функцию.
Тем самым мы закончили описывать базис модели Б, а также основное (и един ственное) средство развития – форму DEF.
2.2.9. Программа вычисления факториала
Продемонстрируем сказанное на примере рекурсивной программы, попрежнему стремясь к идеалу концептуальной ясности.
Рассмотрим задачу вычисления факториала. Конечно, наша цель – не просто запрограммировать факториал, а показать, как это делается с помощью форм. Начнем, как и раньше, с математического определения нужной функции «факто риал» (обозначение – !) и снова применим пошаговую детализацию.
Обычное математическое определение:
!n равен 1, если n = 0; иначе равен n, умноженному на !(n–1).
Как такое определение переписать в стиле Бэкуса?
Функциональное программирование (модель Б)
На первом шаге нужно выявить функции, на которые непосредственно разла
гается исходная функция «факториал». Ее исходное определение не устраивает нас потому, что оно не прямо разлагает факториал на компонентыфункции, а описывает его (факториала) результат через результаты применения некоторых функций.
С учетом сказанного вернемся к исходному определению факториала. Что из
вестно про функцию «!»? То, что она разлагается на различные составные части в зависимости от некоторого свойства аргумента (в зависимости от его равенства нулю).
Следовательно, можно представить факториал условной формой DEF ! :: (р > f;g),
где p,f и g – пока не определенные функции.
Вот и выполнен первый шаг детализации. Теперь займемся введенными функ
циями.
Что известно про р? Это функция, проверяющая равенство нулю исходного
аргумента. Итак, р можно представить в виде композиции функции eq и некото рой пока еще не определенной функции, которая готовит для eq аргументы (точ нее, формально один аргументкортеж из двух содержательных аргументов). По лучим
DEF р :: eq * f1. Что делает fl? Она ведь должна приготовить аргументы для проверки исход
ного аргумента на равенство другому объекту, а именно нулю. Другими словами, ее результатом должна быть пара, составленная из исходного аргумента фактори ала и нуля. Пару естественно получить формой «конструкция». Так что
DEF f1 :: f2 , f3. Что делает f2? Поставляет первый аргумент пары. Но ведь это исходный аргу
мент без изменений! Следовательно,
DEF f2 :: id. А что делает f3? Поставляет второй аргумент пары. Но ведь это нуль! Отлич
но, значит, f3 – постоянная, которую естественно определить через форму const:
DEF f3 :: const (0). Итак, функция р определена полностью. Продолжим детализацию для f и g.
Что делает f? Всегда дает в результате единицу. Значит, это постоянная, кото
рую также легко выразить через const.
DEF f :: const(l). А что делает g? Вычисляет произведение двух объектов, каждый из которых,
как теперь уже нетрудно понять, должен доставляться своей функцией. Значит, g естественно представить композицией функций «mult» (умножить) и некото рой конструкции двух функций:
DEF g :: mult * (gl , g2).
307
308
Очевидно, что g1 совпадает с id (почему?). A g2 представляет собой компози цию определяемой функции «!» и функции g3, вычитающей единицу из исходно го аргумента. Поэтому
DEF g2 :: ! * g3, где g3, в свою очередь, представляется как композиция вычитания и конструкции,
готовящей для этого вычитания аргумент. Позволим себе пропустить шаг подроб ной детализации и сразу написать
DEF g3 :: – * (id , const(1)).
Пошаговая детализация полностью завершена. Программа в модели Б, вы числяющая факториал, написана. Метод пошаговой детализации продемонстри рован. Но, конечно, программа не обязана быть такой длинной, и не все шаги обя зательны. Полученные на промежуточных шагах определения можно убрать, подставив соответствующие формулы вместо обозначений функций. В нашем случае получим следующее функциональное соотношение, определяющее фак ториал:
DEF ! :: eq0 > const(1); mult*(id, !*sub1), где через eq0 переобозначена для наглядности функция р, а через sub1 – функ
ция g3.
Кстати, eq0= (s eq 0). Верно ли, что sub1= (s – 1)?
Замечательное свойство пошаговой детализации (функциональной декомпо зиции) в модели Б состоит в том, что нам ни разу не понадобилось исправлять определения ранее введенных функций. Другими словами, функциональная де композиция не зависит от контекста (она, как говорят, контекстно свободна). Это важнейшее преимущество функционального программирования с точки зрения борьбы со сложностью программ.
Перспективы языков программирования
Вопрос. За счет чего оно достигнуто? Упражнение. Запрограммируйте в стиле Бэкуса другое определение факториала:
!n равен произведению всех различных натуральных чисел, меньших или равных n.
2.2.10. Программа перемножения матриц
Напишем в стиле Бэкуса программу, перемножающую две прямоугольные матри цы (согласованных размеров). Снова применим метод пошаговой детализации. Начнем, как обычно, с постановки задачи, то есть с определения функции MM (matrix multiply), которую предстоит запрограммировать.
Результат умножения двух матриц B1(m,n) и В2(n,k) –
это такая матрица C(m,k), каждый
(def) элемент c(i,j) которой – скалярное
произведение iй строки матрицы В1 на jй столбец матрицы В2.
Функциональное программирование (модель Б)
До сих пор в наших примерах мы имели дело с программами, работающими
с относительно просто устроенными данными. Поэтому можно было не занимать ся проблемой представления данных специально. Между тем в программистском фольклоре бытует крылатая фраза, довольно точно отражающая трудоемкость и значимость отдельных аспектов программирования: «Дайте мне структуру дан ных, а уж программу я и сам напишу!» Мы еще не раз поговорим о данных, а пока займемся представлением исходных данных в нашей задаче, то есть представим матрицы в виде Бобъектов (других типов данных в модели Б просто нет (!) ).
При описании представления нам потребуется говорить о длине объектовкор
тежей. Поэтому введем еще одну примитивную функцию
leng : X :: X =<>> 0;
Х=<Х1, ... ,Хn> > n; <?>.
Функция leng вычисляет число непосредственных компонент объекта X (его
длину как кортежа). Например:
leng:<A,<B,C>,D> = 3, leng:(2:<A,<B,C>,D>) = 2.
Аргумент X функции ММ будет парой (объектом длины 2), компоненты кото
рой представляют исходные матрицы. Так что
1:Х=В1 , 2:Х=В2
(здесь В1 и В2 используются как обозначения матриц, а не как Батомы!). При этом матрицы, в свою очередь, будут представлены кортежами строк. Например, если
В1 = 3 5 7, B2 = 9 5
2 4 6 6 3
1 2,
то
X =<<<3,5,7>,<2,4,6>>,<<9,5>,<6,3>,<1,2>>>. Так что leng:(l:X) = 2, и это число строк в матрице В1,
leng:(l:(l:X)) = 3 (число столбцов в В1), leng:(2:X) =3 (число строк в матрице В2), leng:(l:(2:X)) = 2 (число столбцов в В2).
Итак, мы научились представлять матрицы «по строкам» и говорить об их раз
мерах в терминах «объектного представления». Осталось отметить, что если объект Y представляет матрицу, то ее элемент, стоящий на пересечении iй строки и jгo столбца, можно получить с помощью функции
j * i. При этом элементы матрицы В1 можно получить из X функцией j*i* 1,
а элементы матрицы В2 – функцией
j*i*2.
309
310
Теперь мы готовы приступить к первому шагу функциональной декомпозиции.
Первый шаг. Как и раньше, начнем с «конца» определения (def). Там сказано, что «каждый элемент» результата – это скалярное произведение некоторых объектов. Следовательно, ММ можно определить как композицию двух функций, из которых внутренняя готовит сомножители для скалярных произведений, а внешняя выполняет умножение для всех заготовленных аргументов.
DEF ММ :: f2 * f1.
Можно ли сразу же продолжить функциональную декомпозицию? На первый взгляд можно, тем более что совсем недавно шла речь о ее независимости от кон текста.
Но как же детализировать, например, f2, не зная, какова структура аргумента у этой функции? Все дело в том, что мы по существу еще не завершили предыду щего шага декомпозиции – мало обозначить компоненты функциональной фор мы, нужно еще описать их внешний эффект (или, как говорят, спроектировать их внешнюю спецификацию). Другими словами, нужно решить, каково у каждой из этих функций множество исходных данных, каково множество допустимых ре зультатов и какое именно отображение из первого множества во второе каждая из функций осуществляет.
В нашем случае для ММ все это ясно (и тем самым определены исходные дан ные для f1 и результаты для f2). Нужно «разделить сферы влияния» f1 и f2, вы брав представление для аргумента функции f2 (и тем самым для результата функ ции f1).
Вспомнив (def), нетрудно понять, что строение аргумента функции f2 должно отличаться от строения результата лишь тем, что на местах элементов матрицы С должны находиться те самые пары объектовкортежей, из которых соответствую щие c(i,j) будут получены скалярным умножением. Например, в случае наших В1 и В2 аргумент функции f2 должен представлять собой объект, соответствующий матрице размером 2 на 2, из которого функция, например, 2 * 1 извлекает пару <<3,5,7>,<5,3,2>>. Назовем поэтому аргумент функции f2 матрицей пар. Вот те перь можно продолжить декомпозицию.
На втором шаге займемся функцией f2. Она должна применять функцию IP (скалярное умножение) КО ВСЕМ элементам матрицы пар. Если бы в нашем рас поряжении была форма, которая применяет свой аргументфункцию ко всем эле ментам матрицы (то есть кортежа кортежей), то было бы ясно, как представить f2 через эту форму и IP ( ция А (применяющая заданную ей в качестве аргумента функцию ко всем элемен там кортежа). Таким образом, если ввести определение
DEF f2 :: (A f3), то f2 окажется представленной через общую аппликацию с функцией f3, применя
емой ко всем «строкам» матрицы пар. Осталось обеспечить, чтобы при каждом своем применении f3 применяла IP ко всем элементам «строки» (то есть кортежа пар). Ясно, что нужная функция легко выражается через А и IP:
DEF f3 :: (A IP).
кстати, как это сделать?). Но у нас есть лишь общая апплика
Перспективы языков программирования
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]