Функциональное программирование объясняется на примере диалекта
В настоящий момент функциональное программирование представлено целым семейством языков, но
В некоторых случаях осознанное усвоение концепций даже на самом низком уровне нереально без базовых теоретических сведений. А знакомство с таким базисом, в свою очередь, стимулирует значительно более глубокий интерес к теории и способствует пониманию того, что на высшие уровни знаний и умений не подняться без овладения теорией.
Теоретической основой языка
В $$\lambda$$ -исчислении выразительные средства, на первый взгляд, крайне скупы. Имеются две базисные операции: применение функции к аргументу (fx) и квантор образования функции по выражению $$\lambda x t[x]$$. В терминах $$\lambda$$ -исчисления функция возведения числа в квадрат записывается как $$\lambda x (sqrx)$$ или, если быть ближе к обычным математическим обозначениям, $$\lambda x x^{2}$$.
Основная операция — символьное вычисление применения функции к аргументу: $$(\lambda x t[x] u)$$ преобразуется в t[u]. Но эта операция может применяться в любом месте выражения, так что никакая конкретная дисциплина вычислений не фиксируется. Более того, функции могут вычисляться точно так же, как аргументы. Уже эта маленькая тонкость приводит к принципиальному расширению возможностей $$\lambda$$ -исчисления по сравнению с обычными вызовами процедур. Если мы желаем ограничиться лишь ею, рассматривается типизированное $$\lambda$$ -исчисление, в котором, как принято в большинстве современных систем программирования, значения строго разделены по типам. В типизированном $$\lambda$$ -исчислении есть только типы функций, но этого хватает, поскольку функции могут принимать в качестве параметров и выдавать функции.
Но в исходной своей форме $$\lambda$$ -исчисление является нетипизированным, любой
$$(\lambda x (xx) \lambda x (xx))$$
вычисляется бесконечно, а чуть более сложное выражение
$$((\lambda x \lambda y x a) (\lambda x (xx) \lambda x (xx)))$$
может либо дать a, либо зациклиться, в зависимости от выбора порядка его вычисления. Но все равно, если мы приходим к результату, то он определяется однозначно. Так что совместность вычислений не портит однозначности, если язык хорошо сконструирован.
Основной единицей данных для
() (обозначаемый также nil ) является l1,. . . , ln, n >= 1 — атомы либо (l1, . . . , ln) — также Элементами (l1, . . . , ln) называются l1, . . . , ln. Равенство
l = nil тогда и только тогда, когда l также есть nil.(l1, . . . , ln) = (k1, . . . , km) тогда и только тогда, когда n = m и соответствующие li = ki.Пример 8.2.2. Все (), (()), ((())) и т. д. различны. Различны также и nil, (nil, nil), (nil, nil, nil) и так далее. Попарно различны и ((A,B), C), (A, (B,C)), (A,B,C), где A, B, C — различные атомы.
Поскольку понятие, задаваемое индуктивным определением, должно строиться в результате конечного числа шагов применения определения, мы исключаем nil либо атомы.
Вершины L задаются следующим индуктивным определением.
Длиной (l1, . . . , ln) и (k1, . . . , km) называется
(l1, . . . , ln, k1, . . . , km).
Замена вершины a L на атом либо M получается заменой поддерева L, соответствующего a, на дерево для M. Замена обозначается L[a | M]. Через L[a || M] будем обозначать результат замены нескольких вхождений вершины a на M.
Атомами в языке NIL, который в принципе атомом не является, но в языке NIL, а те, которые работают с атомами, часто к нему неприменимы. Например, попытка присваивания значения выдает ошибку.
Основная операция для задания (list a b . . . z). Она вычисляет свои аргументы и собирает их в ’(a b . . . z). Она является частным случаем функции quote (сокращенно обозначаемой ’ ), которая запрещает всякие вычисления в своем аргументе и копирует его в результат так, как он есть.
По традиции, элементарные операции разбора c и кончаются на r, а в середине идет некоторая последовательность букв a и d ; (car s) выделяет голову (первый член (cdr s) — хвост (подсписок всех членов, начиная со второго). Буквы a и d применяются, начиная с конца. Общее число символов в получающемся атоме должно быть не больше шести. Рассмотрим фрагмент диалога, иллюстрирующий эти операции. Как только в диалоге вводится законченное выражение, оно вычисляется либо выдается ошибка.
[13]>(setq a ’(b c (d e) f g)) (B C (D E) F G) [14]> (cddr a) ((D E) F G) [15]> (cddar a) *** - CDDAR: B is not a list 1. Break [16]> ^Z [17]> (caaddr a) D [18]> (cdaddr a) (E)
Если не применены специальные операции блокирования вычислений, первый аргумент
Таким образом, в
(setq atom value), аналогичная присваиванию. Эта функция не вычисляет свой первый аргумент, она рассматривает его как имя, которому нужно приписать значение.
Значение в языке setf, вычисляющая свой первый аргумент, дающий ссылку на место, которому можно приписать значение (например, на get, даже если
[38]> (setf (get ’b ’weight) ’(125 kg)) (125 KG) [39]> (get ’b ’weight) (125 KG)
Рассмотрим подробнее структуру данных языка
Типы данных (в смысле программирования) в float.
Для
(рис 8.1) Структура информации, сопоставленной атому языка LISP
Их стоит рассматривать как сугубо математические
Общую структуру данных и программы функционального языка можно рассматривать как связный нагруженный граф, динамически изменяющийся в ходе вычислений, у которого имеются активные вершины, т. е. функции, вычисляемые в данный момент, потенциально активные вершины, соответствующие функциям, которым назначено вычисление или продолжение вычисления (отложенного, приостановленного и т. п.), и пассивные вершины, участие которых в вычислениях в данный момент не запланировано.
Конкретизировать такой граф и стратегию отработки активных вершин можно разными способами. При этом могут появляться разные ипостаси функционального программирования.
Для абстрактного вычислителя граф функциональных зависимостей естественно считать
В практике реализации функциональных систем программирования имеется три варианта конкретизации представления графа:
Коммутационные и ассоциативные схемы рассмотрены при обсуждении неимперативных моделей вычислений (см. § 1.2 и 3.1). Выбор последовательно просматриваемой структуры для первого функционального языка обусловлен единственной для того времени возможностью реализации функциональности путем моделирования ее операционными средствами традиционной модели вычислений.
Программа на языке quote запрещает вычисление своего аргумента, функция setq запрещает вычисление лишь первого из двух аргументов, а функция setf заставляет вычислить первый аргумент лишь до стадии, когда получена ссылка на его значение). Любое выражение выдает значение, что используется, в частности, при диалоговой работе с
Основные управляющие функции концептуально едины и позволяют динамически строить блочную структуру программы. В частности, функция
(block name e1 . . . en) (8.1)
вычисляет свои аргументы, начиная со второго, один за другим, тем самым задавая последовательность команд. Первый ее аргумент, name, служит именем блока. В любой момент из любого объемлющего блока можно выйти и выдать значение с помощью функции
(return-from name value) (8.2)
Этим
Далее, блоком считается любое описание функции. Описание функции производится при помощи функции , которая, в свою очередь, определяется через примитивы function и lambda. Первый из них задает, что имя, являющееся его аргументом, рассматривается как функция (он часто сокращается в конкретном синтаксисе до #’ ), второй образует значение функционального типа. Имя функции является и именем функционального блока.
Пример 8.4.1. В данном примере иллюстрируются определение факториала, вызов анонимной функции и возможность вычисления произвольного функционального выражения, созданного в программе.
[1]> (defun fact (n) (if (= n 0) 1
(* (fact (- n 1)) n)))
FACT
[2]> (fact 40)
815915283247897734345611269596115894272000000000
[3]> ((lambda (x) (fact (* x x))) 5)
15511210043330985984000000
[4]> (setq g ’(lambda (x) (fact (* x x))))
(LAMBDA (X) (FACT (* X X)))
[5]> (eval (list g 3))
362880
Нужно заметить, что определение функции с данным именем и значение имени могут задаваться независимо. Например, мы можем в этом же контексте задать (setq fact 7), хотя, конечно же, это отвратительный способ программирования.
Все формальные параметры функций являются локальными переменными. Никакие изменения их значений не выходят наружу. Но все другие свойства остаются глобальными! Приведем пример.
[23]> (defun f (x) (progn (setf
(get ’x ’weight) ’(25 kg)) (+ x 3)))
F
[24]> (setf (get ’x ’weight) ’(30 kg))
(30 KG)
[25]> (get ’x ’weight)
(30 KG)
[26]> (setq x 5)
5
[27]> (f 3)
6
[28]> x
5
[29]> (get ’x ’weight)
(25 KG)
В let.
Значение имени, унаследованного извне, все равно будет внешним! Смотрите пример ниже.
[32]>(setq a ’(b c d))
(B C D)
[33]>(setq b 5)
5
[34]> (list (let ((b 6)) (eval (car a)))
(eval (car a)))
(5 5)
[35]> (list (let ((b 6)) b) (eval (car a)))
(6 5)
[36]> (list (let ((b 6)) (list b a))
(eval (car a)))
((6 (B C D)) 5)
[37]> (list (let ((b 6)) (eval (car
(list ’b a)))) (eval (car a)))
(5 5)
Важнейшей особенностью функционального программирования как стиля, впервые использованной в языке
[57]> (setq a (list 1 5 7 9 11 13 15 19 22 28)) (1 5 7 9 11 13 15 19 22 28) [58]> (mapcar (function (lambda (x) (* x x))) a) (1 25 49 81 121 169 225 361 484 784)
применяет свой первый аргумент ко всем членам второго.
Такие NIL.
И, наконец, приведем
Пример 8.4.2. Данная программа строит автомат для нахождения всех вхождений некоторой системы слов во входной поток. Позже она анализируется с точки зрения
;==================================================
;
; свертка/развертка системы текстов
; текст представлен списком
;((Имя Вариант ...)...)
; первое имя в свертке - обозначение системы текстов
; (Элемент ...)
; (Имя Лексема (Варианты))
; ((пример (ма (ш н)
; (ш а) )
; ( ш н ) )
; ((н ина)) )
;==================================================
; реализация свертки: unic, ass-all, swin, gram, bnf
(defun unic (vac) (remove-duplicates (mapcar ’car vac) ))
;; список уникальных начал
(defun ass-all (Key Vac)
;; список всех вариантов продолжения (что может идти за ключом)
(cond
((Null Vac) Nil)
((eq (caar Vac) Key) (cons (cdar Vac)
(ass-all Key (cdr Vac)) ))
(T (ass-all Key (cdr Vac)) )
) )
(defun swin (key varl) (cond
;; очередной шаг свертки или снять скобки при отсутствии вариантов
((null (cdr varl))(cons key (car varl)))
(T (list key (gram varl)) )
))
(defun gram (ltext)
;; левая свертка, если нашлись общие начала
( (lambda (lt) (cond
((eq (length lt)(length ltext)) ltext)
(T (mapcar
#’(lambda (k) (swin k (ass-all k ltext ) ))
lt )
) ) ) (unic ltext)
) )
(defun bnf (main ltext binds) (cons (cons main (gram ltext)) binds))
;; приведение к виду БНФ
;===================================================
; реализация развертки: names, words, lexs, d-lex, d-names,
; h-all, all-t, pred, sb-nm, chain, level1, lang
(defun names (vac) (mapcar ’car vac))
;; определяемые символы
(defun words (vac) (cond
;; используемые символы
((null vac) NIL)
((atom vac) (cons vac NIL ))
(T (union (words(car vac)) (words (cdr vac)))) ))
(defun lexs (vac) (set-difference (words vac) (names vac)))
;; неопределяемые лексемы
(defun d-lex ( llex)
;; самоопределение терминалов
(mapcar #’(lambda (x) (set x x) ) llex) )
(defun ( llex)
;; определение нетерминалов
(mapcar #’(lambda (x) (set (car x )(cdr x )) ) llex) )
(defun h-all (h lt)
;; подстановка голов
(mapcar #’(lambda (a)
(cond
((atom h) (cons h a))
(T (append h a)) )
) lt) )
(defun all-t (lt tl)
;; подстановка хвостов
(mapcar #’(lambda (d)
(cond
((atom d) (cons d tl))
(T(append d tl))
) ) lt) )
(defun pred (bnf tl)
;; присоединение предшественников
(level1 (mapcar #’(lambda (z) (chain z tl )) bnf) ))
(defun sb-nm (elm tl)
;; постановка определений имен
(cond
((atom (eval elm)) (h-all (eval elm) tl))
(T (chain (eval elm) tl))
) )
(defun chain (chl tl)
;; сборка цепочек
(cond
((null chl) tl)
((atom chl) (sb-nm chl tl))
((atom (car chl))
(sb-nm (car chl) (chain (cdr chl) tl) ))
(T (pred (all-t (car chl) (cdr chl)) tl)) ))
(defun level1 (ll)
;; выравнивание
(cond
((null ll)NIL)
(T (append (car ll) (level1 (cdr ll)) )) ))
(defun lang ( frm )
;; вывод заданной системы текстов
(d-lex (lexs frm))
(d-names frm)
(pred (eval (caar frm)) ’(())
) )
Разберем возможности языка
Выразительные средства конкретно-синтаксического представления общей структуры данных и программ языка
Реализационное представление как нельзя лучше соответствует соглашению об общности eval, заставляющую eval обязана быть
универсально применимой к любому
К сожалению, такой универсализм провоцирует крайне ненадежное и неэффективное программирование, поэтому это решение нельзя считать удачным. Справедливости ради заметим, что в те времена, когда разрабатывался язык, задача надежности еще не была поставлена, но плохо то, что сформировался стихийный стандарт, не способствующий качеству программирования.
Для обеспечения практической пользы функции eval следовало бы предусмотреть компенсирующие регламенты ее корректного применения на уровне конкретного синтаксиса, режимов вычислений и системных механизмов.
Внимание!
На уровне абстрактного и конкретного синтаксиса разные семантические возможности имеют разный статус, поэтому в конкретном представлении необходимо предусматривать механизм скрытия и даже полного запрета тех возможностей, которые концептуально разумны лишь на уровне
Структура
Другие гипотетические кандидаты на роль конкретного синтаксиса по этому критерию явно проигрывают. Традиционные математические формы задания функций и их применений являются текстуально избыточными (как префиксная, так и постфиксная записи требуют обязательного обрамления параметров скобками), а бесскобочная нотация Лукасевича (и прямая, и обратная) еще более запутывали бы тексты по сравнению с "утомительным нагромождением скобок". Но за счет внеязыковых прагматических соглашений о том, как располагать на двумерном носителе (на бумаге или на экране) скобочную структуру, можно существенно облегчить. Если же система программирования будет поддерживать (и проверять!) прагматические соглашения (что характерно для развитых систем), то вид программ станет вполне читаемым. Таким образом преодолеваются неудобства линейного представления.
Сегодня можно было бы говорить о других форматах конкретного синтаксиса
Диктат линейности укоренился настолько глубоко, что даже в тех случаях, когда он мог бы быть преодолен безболезненно, языковая система чаще всего все равно строится как линейная. Это касается не только
Стандартная надстройка над
Начнем с понятия структуры данных в языке defstruct вида
(defstruct
Задание структуры автоматически задает функцию-конструктор структуры make-, которая может принимать ключевые аргументы для каждого из полей:
(make-
и функцию доступа для каждого из полей, например , использующуюся для получения значения поля или ссылки на него. Если поле не инициализировано (ни по умолчанию, ни конструктором), оно получает начальное значение NIL. Никакой
В
(defclass pet (animal possession) (
(species :initform ’cat)
(nick :accessor nickof
:inintform ’Pussy
:initarg namepet)
)
Этот класс наследует поля, функции доступа и прочее от классов animal и possession. Например, поле cost имеется в значении класса, если оно имеется в одном из этих классов. Поскольку
Основная функция наследования в
[6]> (defclass init () ())
#<STANDARD-CLASS INIT>
[7]> (defclass a (init) ())
#<STANDARD-CLASS A>
[8]> (defclass b (init) ())
#<STANDARD-CLASS B>
[9]> (defclass c1 (a b) ())
#<STANDARD-CLASS C1>
[10]> (defclass c2 (b a) ())
#<STANDARD-CLASS C2>
[11]> (defclass contr (c1 c2) ())
*** - DEFCLASS CONTR:
inconsistent precedence graph,
cycle (#<STANDARD-CLASS A> #<STANDARD-CLASS B>)
В
(defmethod inspectpet ((x pet) (y float)) (setf weightofanimal 3.5))
Как видно из этого примера, методы не обязательно связаны с классами. Они могут быть связаны с любыми типами. Методы в
(defclass thing ()
((weight :initform ’(0 kg)
:accessor weightof
:initarg :weight)))
(defclass animal (thing)
((specie :accessor specieof
:initarg :spec)
(sex :accessor sexof
:initform ’m
:initarg :sex)))
(defclass possession (thing)
((owner :accessor ownerof
:initform ’nnn)
(cost :accessor costof
:initform ’(0 bucks)
:initarg :cost))
)
(defclass person (animal)
((specie :initform ’human)
(name :initarg :thename
:accessor nameof)))
(defclass pet (animal possession)
((nick :initarg :thenick
:accessor nickof)
(specie :initform ’cat)))
(defmethod act :before ((p pet))
(print "Cat mews"))
(defmethod act :after ((p pet))
(print "Cat turns"))
(defmethod act :around ((p pet))
(progn (print "You have a cat") (call-next-method)))
(defmethod act ((p animal))
(progn (print "Animal is close to you") (call-next-method)))
(defmethod act :before ((p animal))
(print "You see an animal"))
(defmethod act :after ((p animal))
(print "You send the animal off"))
(defmethod act :around ((p animal))
(progn (print "You don’t like wild animals") (call-next-method)))
(defmethod act ((p possession))
(progn (print "You test your property") (call-next-method)))
(defmethod act :before ((p possession))
(print "You see your property"))
(defmethod act :after ((p possession))
(print "You are pleased by your property"))
(defmethod act :around ((p possession))
(progn (print "You admire your property
if it is in good state") (call-next-method)))
(defmethod act ((p thing))
(print "You take the thing"))
(defmethod act :before ((p thing))
(print "You see something"))
(defmethod act :after ((p thing))
(print "You identified this thing"))
(defmethod act :around ((p thing))
(progn (print "You are not interested
in strange things") (call-next-method)))
(act (make-instance ’pet :thenick "Viola" :cost ’(25 kop)))
При загрузке этого файла происходит следующее:
[1]> (load ’myclasses) ;; Loading file E:\clisp-2000-03-06\myclasses.lsp ... "You have a cat" "You don’t like wild animals" "You admire your property if it is in good state" "You are not interested in strange things" "Cat mews" "You see an animal" "You see your property" "You see something" "Cat purrs" "Animal is close to you" "You test your property" "You take the thing" "You identified this thing" "You are pleased by your property" "You send the animal off" "Cat turns" ;; Loading of file E:\clisp-2000-03-06\myclasses.lsp is finished. T
Видно, что упорядоченность классов по отношению наследования позволяет выстраивать целые последовательности действий при вызове одного метода.
Поскольку в
Неадекватное теоретизирование мешает увидеть и развить реальные достоинства системы и закрепляет слабые места.
Функциональное программирование объясняется на примере диалекта
В настоящий момент функциональное программирование представлено целым семейством языков, но
В некоторых случаях осознанное усвоение концепций даже на самом низком уровне нереально без базовых теоретических сведений. А знакомство с таким базисом, в свою очередь, стимулирует значительно более глубокий интерес к теории и способствует пониманию того, что на высшие уровни знаний и умений не подняться без овладения теорией.
Теоретической основой языка
В $$\lambda$$ -исчислении выразительные средства, на первый взгляд, крайне скупы. Имеются две базисные операции: применение функции к аргументу (fx) и квантор образования функции по выражению $$\lambda x t[x]$$. В терминах $$\lambda$$ -исчисления функция возведения числа в квадрат записывается как $$\lambda x (sqrx)$$ или, если быть ближе к обычным математическим обозначениям, $$\lambda x x^{2}$$.
Основная операция — символьное вычисление применения функции к аргументу: $$(\lambda x t[x] u)$$ преобразуется в t[u]. Но эта операция может применяться в любом месте выражения, так что никакая конкретная дисциплина вычислений не фиксируется. Более того, функции могут вычисляться точно так же, как аргументы. Уже эта маленькая тонкость приводит к принципиальному расширению возможностей $$\lambda$$ -исчисления по сравнению с обычными вызовами процедур. Если мы желаем ограничиться лишь ею, рассматривается типизированное $$\lambda$$ -исчисление, в котором, как принято в большинстве современных систем программирования, значения строго разделены по типам. В типизированном $$\lambda$$ -исчислении есть только типы функций, но этого хватает, поскольку функции могут принимать в качестве параметров и выдавать функции.
Но в исходной своей форме $$\lambda$$ -исчисление является нетипизированным, любой
$$(\lambda x (xx) \lambda x (xx))$$
вычисляется бесконечно, а чуть более сложное выражение
$$((\lambda x \lambda y x a) (\lambda x (xx) \lambda x (xx)))$$
может либо дать a, либо зациклиться, в зависимости от выбора порядка его вычисления. Но все равно, если мы приходим к результату, то он определяется однозначно. Так что совместность вычислений не портит однозначности, если язык хорошо сконструирован.
Основной единицей данных для
() (обозначаемый также nil ) является l1,. . . , ln, n >= 1 — атомы либо (l1, . . . , ln) — также Элементами (l1, . . . , ln) называются l1, . . . , ln. Равенство
l = nil тогда и только тогда, когда l также есть nil.(l1, . . . , ln) = (k1, . . . , km) тогда и только тогда, когда n = m и соответствующие li = ki.Пример 8.2.2. Все (), (()), ((())) и т. д. различны. Различны также и nil, (nil, nil), (nil, nil, nil) и так далее. Попарно различны и ((A,B), C), (A, (B,C)), (A,B,C), где A, B, C — различные атомы.
Поскольку понятие, задаваемое индуктивным определением, должно строиться в результате конечного числа шагов применения определения, мы исключаем nil либо атомы.
Вершины L задаются следующим индуктивным определением.
Длиной (l1, . . . , ln) и (k1, . . . , km) называется
(l1, . . . , ln, k1, . . . , km).
Замена вершины a L на атом либо M получается заменой поддерева L, соответствующего a, на дерево для M. Замена обозначается L[a | M]. Через L[a || M] будем обозначать результат замены нескольких вхождений вершины a на M.
Атомами в языке NIL, который в принципе атомом не является, но в языке NIL, а те, которые работают с атомами, часто к нему неприменимы. Например, попытка присваивания значения выдает ошибку.
Основная операция для задания (list a b . . . z). Она вычисляет свои аргументы и собирает их в ’(a b . . . z). Она является частным случаем функции quote (сокращенно обозначаемой ’ ), которая запрещает всякие вычисления в своем аргументе и копирует его в результат так, как он есть.
По традиции, элементарные операции разбора c и кончаются на r, а в середине идет некоторая последовательность букв a и d ; (car s) выделяет голову (первый член (cdr s) — хвост (подсписок всех членов, начиная со второго). Буквы a и d применяются, начиная с конца. Общее число символов в получающемся атоме должно быть не больше шести. Рассмотрим фрагмент диалога, иллюстрирующий эти операции. Как только в диалоге вводится законченное выражение, оно вычисляется либо выдается ошибка.
[13]>(setq a ’(b c (d e) f g)) (B C (D E) F G) [14]> (cddr a) ((D E) F G) [15]> (cddar a) *** - CDDAR: B is not a list 1. Break [16]> ^Z [17]> (caaddr a) D [18]> (cdaddr a) (E)
Если не применены специальные операции блокирования вычислений, первый аргумент
Таким образом, в
(setq atom value), аналогичная присваиванию. Эта функция не вычисляет свой первый аргумент, она рассматривает его как имя, которому нужно приписать значение.
Значение в языке setf, вычисляющая свой первый аргумент, дающий ссылку на место, которому можно приписать значение (например, на get, даже если
[38]> (setf (get ’b ’weight) ’(125 kg)) (125 KG) [39]> (get ’b ’weight) (125 KG)
Рассмотрим подробнее структуру данных языка
Типы данных (в смысле программирования) в float.
Для
(рис 8.1) Структура информации, сопоставленной атому языка LISP
Их стоит рассматривать как сугубо математические
Общую структуру данных и программы функционального языка можно рассматривать как связный нагруженный граф, динамически изменяющийся в ходе вычислений, у которого имеются активные вершины, т. е. функции, вычисляемые в данный момент, потенциально активные вершины, соответствующие функциям, которым назначено вычисление или продолжение вычисления (отложенного, приостановленного и т. п.), и пассивные вершины, участие которых в вычислениях в данный момент не запланировано.
Конкретизировать такой граф и стратегию отработки активных вершин можно разными способами. При этом могут появляться разные ипостаси функционального программирования.
Для абстрактного вычислителя граф функциональных зависимостей естественно считать
В практике реализации функциональных систем программирования имеется три варианта конкретизации представления графа:
Коммутационные и ассоциативные схемы рассмотрены при обсуждении неимперативных моделей вычислений (см. § 1.2 и 3.1). Выбор последовательно просматриваемой структуры для первого функционального языка обусловлен единственной для того времени возможностью реализации функциональности путем моделирования ее операционными средствами традиционной модели вычислений.
Программа на языке quote запрещает вычисление своего аргумента, функция setq запрещает вычисление лишь первого из двух аргументов, а функция setf заставляет вычислить первый аргумент лишь до стадии, когда получена ссылка на его значение). Любое выражение выдает значение, что используется, в частности, при диалоговой работе с
Основные управляющие функции концептуально едины и позволяют динамически строить блочную структуру программы. В частности, функция
(block name e1 . . . en) (8.1)
вычисляет свои аргументы, начиная со второго, один за другим, тем самым задавая последовательность команд. Первый ее аргумент, name, служит именем блока. В любой момент из любого объемлющего блока можно выйти и выдать значение с помощью функции
(return-from name value) (8.2)
Этим
Далее, блоком считается любое описание функции. Описание функции производится при помощи функции , которая, в свою очередь, определяется через примитивы function и lambda. Первый из них задает, что имя, являющееся его аргументом, рассматривается как функция (он часто сокращается в конкретном синтаксисе до #’ ), второй образует значение функционального типа. Имя функции является и именем функционального блока.
Пример 8.4.1. В данном примере иллюстрируются определение факториала, вызов анонимной функции и возможность вычисления произвольного функционального выражения, созданного в программе.
[1]> (defun fact (n) (if (= n 0) 1
(* (fact (- n 1)) n)))
FACT
[2]> (fact 40)
815915283247897734345611269596115894272000000000
[3]> ((lambda (x) (fact (* x x))) 5)
15511210043330985984000000
[4]> (setq g ’(lambda (x) (fact (* x x))))
(LAMBDA (X) (FACT (* X X)))
[5]> (eval (list g 3))
362880
Нужно заметить, что определение функции с данным именем и значение имени могут задаваться независимо. Например, мы можем в этом же контексте задать (setq fact 7), хотя, конечно же, это отвратительный способ программирования.
Все формальные параметры функций являются локальными переменными. Никакие изменения их значений не выходят наружу. Но все другие свойства остаются глобальными! Приведем пример.
[23]> (defun f (x) (progn (setf
(get ’x ’weight) ’(25 kg)) (+ x 3)))
F
[24]> (setf (get ’x ’weight) ’(30 kg))
(30 KG)
[25]> (get ’x ’weight)
(30 KG)
[26]> (setq x 5)
5
[27]> (f 3)
6
[28]> x
5
[29]> (get ’x ’weight)
(25 KG)
В let.
Значение имени, унаследованного извне, все равно будет внешним! Смотрите пример ниже.
[32]>(setq a ’(b c d))
(B C D)
[33]>(setq b 5)
5
[34]> (list (let ((b 6)) (eval (car a)))
(eval (car a)))
(5 5)
[35]> (list (let ((b 6)) b) (eval (car a)))
(6 5)
[36]> (list (let ((b 6)) (list b a))
(eval (car a)))
((6 (B C D)) 5)
[37]> (list (let ((b 6)) (eval (car
(list ’b a)))) (eval (car a)))
(5 5)
Важнейшей особенностью функционального программирования как стиля, впервые использованной в языке
[57]> (setq a (list 1 5 7 9 11 13 15 19 22 28)) (1 5 7 9 11 13 15 19 22 28) [58]> (mapcar (function (lambda (x) (* x x))) a) (1 25 49 81 121 169 225 361 484 784)
применяет свой первый аргумент ко всем членам второго.
Такие NIL.
И, наконец, приведем
Пример 8.4.2. Данная программа строит автомат для нахождения всех вхождений некоторой системы слов во входной поток. Позже она анализируется с точки зрения
;==================================================
;
; свертка/развертка системы текстов
; текст представлен списком
;((Имя Вариант ...)...)
; первое имя в свертке - обозначение системы текстов
; (Элемент ...)
; (Имя Лексема (Варианты))
; ((пример (ма (ш н)
; (ш а) )
; ( ш н ) )
; ((н ина)) )
;==================================================
; реализация свертки: unic, ass-all, swin, gram, bnf
(defun unic (vac) (remove-duplicates (mapcar ’car vac) ))
;; список уникальных начал
(defun ass-all (Key Vac)
;; список всех вариантов продолжения (что может идти за ключом)
(cond
((Null Vac) Nil)
((eq (caar Vac) Key) (cons (cdar Vac)
(ass-all Key (cdr Vac)) ))
(T (ass-all Key (cdr Vac)) )
) )
(defun swin (key varl) (cond
;; очередной шаг свертки или снять скобки при отсутствии вариантов
((null (cdr varl))(cons key (car varl)))
(T (list key (gram varl)) )
))
(defun gram (ltext)
;; левая свертка, если нашлись общие начала
( (lambda (lt) (cond
((eq (length lt)(length ltext)) ltext)
(T (mapcar
#’(lambda (k) (swin k (ass-all k ltext ) ))
lt )
) ) ) (unic ltext)
) )
(defun bnf (main ltext binds) (cons (cons main (gram ltext)) binds))
;; приведение к виду БНФ
;===================================================
; реализация развертки: names, words, lexs, d-lex, d-names,
; h-all, all-t, pred, sb-nm, chain, level1, lang
(defun names (vac) (mapcar ’car vac))
;; определяемые символы
(defun words (vac) (cond
;; используемые символы
((null vac) NIL)
((atom vac) (cons vac NIL ))
(T (union (words(car vac)) (words (cdr vac)))) ))
(defun lexs (vac) (set-difference (words vac) (names vac)))
;; неопределяемые лексемы
(defun d-lex ( llex)
;; самоопределение терминалов
(mapcar #’(lambda (x) (set x x) ) llex) )
(defun ( llex)
;; определение нетерминалов
(mapcar #’(lambda (x) (set (car x )(cdr x )) ) llex) )
(defun h-all (h lt)
;; подстановка голов
(mapcar #’(lambda (a)
(cond
((atom h) (cons h a))
(T (append h a)) )
) lt) )
(defun all-t (lt tl)
;; подстановка хвостов
(mapcar #’(lambda (d)
(cond
((atom d) (cons d tl))
(T(append d tl))
) ) lt) )
(defun pred (bnf tl)
;; присоединение предшественников
(level1 (mapcar #’(lambda (z) (chain z tl )) bnf) ))
(defun sb-nm (elm tl)
;; постановка определений имен
(cond
((atom (eval elm)) (h-all (eval elm) tl))
(T (chain (eval elm) tl))
) )
(defun chain (chl tl)
;; сборка цепочек
(cond
((null chl) tl)
((atom chl) (sb-nm chl tl))
((atom (car chl))
(sb-nm (car chl) (chain (cdr chl) tl) ))
(T (pred (all-t (car chl) (cdr chl)) tl)) ))
(defun level1 (ll)
;; выравнивание
(cond
((null ll)NIL)
(T (append (car ll) (level1 (cdr ll)) )) ))
(defun lang ( frm )
;; вывод заданной системы текстов
(d-lex (lexs frm))
(d-names frm)
(pred (eval (caar frm)) ’(())
) )
Разберем возможности языка
Выразительные средства конкретно-синтаксического представления общей структуры данных и программ языка
Реализационное представление как нельзя лучше соответствует соглашению об общности eval, заставляющую eval обязана быть
универсально применимой к любому
К сожалению, такой универсализм провоцирует крайне ненадежное и неэффективное программирование, поэтому это решение нельзя считать удачным. Справедливости ради заметим, что в те времена, когда разрабатывался язык, задача надежности еще не была поставлена, но плохо то, что сформировался стихийный стандарт, не способствующий качеству программирования.
Для обеспечения практической пользы функции eval следовало бы предусмотреть компенсирующие регламенты ее корректного применения на уровне конкретного синтаксиса, режимов вычислений и системных механизмов.
Внимание!
На уровне абстрактного и конкретного синтаксиса разные семантические возможности имеют разный статус, поэтому в конкретном представлении необходимо предусматривать механизм скрытия и даже полного запрета тех возможностей, которые концептуально разумны лишь на уровне
Структура
Другие гипотетические кандидаты на роль конкретного синтаксиса по этому критерию явно проигрывают. Традиционные математические формы задания функций и их применений являются текстуально избыточными (как префиксная, так и постфиксная записи требуют обязательного обрамления параметров скобками), а бесскобочная нотация Лукасевича (и прямая, и обратная) еще более запутывали бы тексты по сравнению с "утомительным нагромождением скобок". Но за счет внеязыковых прагматических соглашений о том, как располагать на двумерном носителе (на бумаге или на экране) скобочную структуру, можно существенно облегчить. Если же система программирования будет поддерживать (и проверять!) прагматические соглашения (что характерно для развитых систем), то вид программ станет вполне читаемым. Таким образом преодолеваются неудобства линейного представления.
Сегодня можно было бы говорить о других форматах конкретного синтаксиса
Диктат линейности укоренился настолько глубоко, что даже в тех случаях, когда он мог бы быть преодолен безболезненно, языковая система чаще всего все равно строится как линейная. Это касается не только
Стандартная надстройка над
Начнем с понятия структуры данных в языке defstruct вида
(defstruct
Задание структуры автоматически задает функцию-конструктор структуры make-, которая может принимать ключевые аргументы для каждого из полей:
(make-
и функцию доступа для каждого из полей, например , использующуюся для получения значения поля или ссылки на него. Если поле не инициализировано (ни по умолчанию, ни конструктором), оно получает начальное значение NIL. Никакой
В
(defclass pet (animal possession) (
(species :initform ’cat)
(nick :accessor nickof
:inintform ’Pussy
:initarg namepet)
)
Этот класс наследует поля, функции доступа и прочее от классов animal и possession. Например, поле cost имеется в значении класса, если оно имеется в одном из этих классов. Поскольку
Основная функция наследования в
[6]> (defclass init () ())
#<STANDARD-CLASS INIT>
[7]> (defclass a (init) ())
#<STANDARD-CLASS A>
[8]> (defclass b (init) ())
#<STANDARD-CLASS B>
[9]> (defclass c1 (a b) ())
#<STANDARD-CLASS C1>
[10]> (defclass c2 (b a) ())
#<STANDARD-CLASS C2>
[11]> (defclass contr (c1 c2) ())
*** - DEFCLASS CONTR:
inconsistent precedence graph,
cycle (#<STANDARD-CLASS A> #<STANDARD-CLASS B>)
В
(defmethod inspectpet ((x pet) (y float)) (setf weightofanimal 3.5))
Как видно из этого примера, методы не обязательно связаны с классами. Они могут быть связаны с любыми типами. Методы в
(defclass thing ()
((weight :initform ’(0 kg)
:accessor weightof
:initarg :weight)))
(defclass animal (thing)
((specie :accessor specieof
:initarg :spec)
(sex :accessor sexof
:initform ’m
:initarg :sex)))
(defclass possession (thing)
((owner :accessor ownerof
:initform ’nnn)
(cost :accessor costof
:initform ’(0 bucks)
:initarg :cost))
)
(defclass person (animal)
((specie :initform ’human)
(name :initarg :thename
:accessor nameof)))
(defclass pet (animal possession)
((nick :initarg :thenick
:accessor nickof)
(specie :initform ’cat)))
(defmethod act :before ((p pet))
(print "Cat mews"))
(defmethod act :after ((p pet))
(print "Cat turns"))
(defmethod act :around ((p pet))
(progn (print "You have a cat") (call-next-method)))
(defmethod act ((p animal))
(progn (print "Animal is close to you") (call-next-method)))
(defmethod act :before ((p animal))
(print "You see an animal"))
(defmethod act :after ((p animal))
(print "You send the animal off"))
(defmethod act :around ((p animal))
(progn (print "You don’t like wild animals") (call-next-method)))
(defmethod act ((p possession))
(progn (print "You test your property") (call-next-method)))
(defmethod act :before ((p possession))
(print "You see your property"))
(defmethod act :after ((p possession))
(print "You are pleased by your property"))
(defmethod act :around ((p possession))
(progn (print "You admire your property
if it is in good state") (call-next-method)))
(defmethod act ((p thing))
(print "You take the thing"))
(defmethod act :before ((p thing))
(print "You see something"))
(defmethod act :after ((p thing))
(print "You identified this thing"))
(defmethod act :around ((p thing))
(progn (print "You are not interested
in strange things") (call-next-method)))
(act (make-instance ’pet :thenick "Viola" :cost ’(25 kop)))
При загрузке этого файла происходит следующее:
[1]> (load ’myclasses) ;; Loading file E:\clisp-2000-03-06\myclasses.lsp ... "You have a cat" "You don’t like wild animals" "You admire your property if it is in good state" "You are not interested in strange things" "Cat mews" "You see an animal" "You see your property" "You see something" "Cat purrs" "Animal is close to you" "You test your property" "You take the thing" "You identified this thing" "You are pleased by your property" "You send the animal off" "Cat turns" ;; Loading of file E:\clisp-2000-03-06\myclasses.lsp is finished. T
Видно, что упорядоченность классов по отношению наследования позволяет выстраивать целые последовательности действий при вызове одного метода.
Поскольку в
Неадекватное теоретизирование мешает увидеть и развить реальные достоинства системы и закрепляет слабые места.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.