Ремонт принтеров, сканнеров, факсов и остальной офисной техники


назад Оглавление вперед




[129]

Упражнение 4.50.

Реализуйте новую особую форму ramb, которая подобна amb, однако перебирает варианты не слева направо, а в случайном порядке. Покажите, как такая форма может пригодиться в Лизиной задаче из упражнения 4.49

Упражнение 4.51.

Реализуйте новую разновидность присваивания permanent-set! - присваивание, которое не отменяется при неудачах. Например, можно выбрать два различных элемента в списке и посчитать, сколько для этого потребовалось попыток, следующим образом:

(define count 0)

(let ((x (an-element-of (a b c))) (y (an-element-of (a b c))))

(permanent-set! count (+ count 1))

(require (not (eq? x y)))

(list x y count)) ;;; Начало новой задачи ;;; Значение Amb-Eval: (a b 2)

;;; Ввод Amb-Eval: try-again

;;; Значение Amb-Eval: (a c 3)

Какие значения были бы напечатаны, если бы мы вместо permanent-set! использовали здесь обычный set! ?

Упражнение 4.52.

Реализуйте новую конструкцию if-fail, которая позволяет пользователю перехватить неудачу при выполнении выражения. If-fail принимает два выражения. Первое она выполняет как обычно и, если вычисление успешно, возвращает его результат. Однако если вычисление неудачно, то возвращается значение второго выражения, как в следующем примере:

;;; Ввод Amb-Eval:

(if-fail (let ((x (an-element-of (1 3 5)))) (require (even? x))

x)

all-odd)

;;; Начало новой задачи ;;; Значение Amb-Eval: all-odd

;;; Ввод Amb-Eval:

(if-fail (let ((x (an-element-of (1 3 5 8)))) (require (even? x))

x)

all-odd)

;;; Начало новой задачи ;;; Значение Amb-Eval: 8


Упражнение 4.53.

Если у нас есть permanent-set!, описанное в упражнении 4.51, и if-fail из упражнения 4.52, то каков будет результат вычисления

(let ((pairs ()))

(if-fail (let ((p (prime-sum-pair (1358) (20 35 110)))) (permanent-set! pairs (cons p pairs))

(amb))

pairs))

Упражнение 4.54.

Если бы мы не догадались, что конструкцию require можно реализовать как обычную процедуру с помощью amb, так что пользователь сам может определить ее в своей недетерминистской программе, то нам пришлось бы задать эту конструкцию в виде особой формы. Потребовались бы синтаксические процедуры

(define (require? exp) (tagged-list? exp require))

(define (require-predicate exp) (cadr exp))

новая ветвь разбора в analyze:

((require? exp) (analyze-require exp))

а также процедура analyze-require, которая обрабатывает выражения require. Допишите следующее определение analyze-require:

(define (analyze-require exp)

(let ((pproc (analyze (require-predicate exp)))) (lambda (env succeed fail) (pproc env

(lambda (pred-value fail2) (if {??) (??)

(succeed ok fail2)))

fail))))

4.4 Логическое программирование

В главе 1 мы подчеркивали, что информатика имеет дело с императивным знанием («как сделать»), в то время как математика имеет дело с декларативным знанием («что такое»). Действительно, языки программирования требуют, чтобы программист, выражая свои знания, указывал методы пошагового решения определенных задач. С другой стороны, языки высокого уровня обеспечивают в рамках своих реализаций существенный объем методологических знаний, которые освобождает пользователя от забот о многих деталях того, как проходит описываемое вычисление.

Большинство языков программирования, включая Лисп, построены вокруг вычисления значений математических функций. Языки, ориентированные на выражения, (такие, как Лисп, Фортран и Алгол) пользуются тем, что выражение, описывающее значение функции, можно интерпретировать и как способ вычислить это значение. По этой причине большинство языков программирования имеют уклон в однонаправленные вычисления (вычисления со строго определенными входом и выходом). Имеются, однако, совсем другие языки программирования, в которых этот уклон ослаблен. Пример такого языка мы видели


в разделе 3.3.5, где объектами вычисления были арифметические ограничения. В системе ограничений направление и порядок вычислений определены не столь четко; стало быть, чтобы провести вычисление, система должна содержать в себе более детальное знание «как сделать», чем в случае с обычным арифметическим вычислением. Однако это не значит, что пользователь вовсе не отвечает за то, чтобы обеспечить систему императивным знанием. Существует множество сетей, которые задают одно и то же множество ограничений, и пользователю нужно выбрать из множества математически эквивалентных сетей одну подходящую, чтобы описать нужное вычисление.

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

Когда этот подход работает, он служит весьма мощным способом написания программ. Отчасти эта мощь проистекает из того, что один факт вида «что такое» можно использовать для решения нескольких различных задач с разными компонентами «как сделать». Для примера рассмотрим операцию append, которая в качестве аргументов принимает два списка и объединяет их элементы в один список. В процедурном языке вроде Лиспа можно определить append через базовый конструктор списков cons, как в разделе 2.2.1:

(define (append x y)

(if (null? x) y

(cons (car x) (append (cdr x) y))))

Эту процедуру можно рассматривать как перевод на Лисп следующих двух правил; первое покрывает случай, когда первый список пуст, а второе - случай

58Логическое программирование выросло из долгой традиции исследований по автоматическому доказательству теорем. Ранние программы доказательства теорем достигали лишь скромных результатов, так как они полностью перебирали пространство возможных доказательств. Крупный прорыв, который сделал такой поиск осмысленным, случился в начале 1960х годов, когда были открыты алгоритм унификации (unification algorithm) и принцип резолюции (resolution principle) (Robinson 1965). Резолюцию использовали, например, Грин и Рафаэль (Green and Raphael 1968, см. также Green 1969) как основу дедуктивной системы вопрос-ответ. Большую часть этого периода исследователи сосредотачивались на алгоритмах, которые гарантированно находят решение, если оно существует. Такими алгоритмами было трудно управлять, и трудно было указать им направление доказательства. Хьюитт (Hewitt 1969) нашел возможность сочетать управляющую структуру языка программирования с операциями системы логического манипулирования, и это привело к появлению работы по автоматическому поиску, упомянутой в разделе 4.3.1 (примечание 47). В то же самое время в Марселе Кольмероэ разрабатывал системы обработки естественного языка, основанные на правилах (см. Colmerauer et al. 1973). Для представления этих правил он изобрел язык Пролог. Ковальски (Kowalski 1973; Kowalski 1979) в Эдинбурге обнаружил, что выполнение программы на Прологе можно интерпретировать как доказательство теорем (с использованием метода доказательства, называемого линейной резолюцией Хорновских форм). Слияние этих двух линий привело к возникновению традиции логического программирования. Таким образом, в споре о приоритетах в области логического программирования французы могут указать на корни Пролога в Марсельском университете, а британцы на работы, сделанные в университете Эдинбурга. А по мнению исследователей из MIT, обе эти группы разработали логическое программирование, когда пытались понять, что же хотел сказать Хьюитт в своей блистательной, но трудночитаемой диссертации. Историю логического программирования можно найти в Robinson 1983.



[стр.Начало] [стр.1] [стр.2] [стр.3] [стр.4] [стр.5] [стр.6] [стр.7] [стр.8] [стр.9] [стр.10] [стр.11] [стр.12] [стр.13] [стр.14] [стр.15] [стр.16] [стр.17] [стр.18] [стр.19] [стр.20] [стр.21] [стр.22] [стр.23] [стр.24] [стр.25] [стр.26] [стр.27] [стр.28] [стр.29] [стр.30] [стр.31] [стр.32] [стр.33] [стр.34] [стр.35] [стр.36] [стр.37] [стр.38] [стр.39] [стр.40] [стр.41] [стр.42] [стр.43] [стр.44] [стр.45] [стр.46] [стр.47] [стр.48] [стр.49] [стр.50] [стр.51] [стр.52] [стр.53] [стр.54] [стр.55] [стр.56] [стр.57] [стр.58] [стр.59] [стр.60] [стр.61] [стр.62] [стр.63] [стр.64] [стр.65] [стр.66] [стр.67] [стр.68] [стр.69] [стр.70] [стр.71] [стр.72] [стр.73] [стр.74] [стр.75] [стр.76] [стр.77] [стр.78] [стр.79] [стр.80] [стр.81] [стр.82] [стр.83] [стр.84] [стр.85] [стр.86] [стр.87] [стр.88] [стр.89] [стр.90] [стр.91] [стр.92] [стр.93] [стр.94] [стр.95] [стр.96] [стр.97] [стр.98] [стр.99] [стр.100] [стр.101] [стр.102] [стр.103] [стр.104] [стр.105] [стр.106] [стр.107] [стр.108] [стр.109] [стр.110] [стр.111] [стр.112] [стр.113] [стр.114] [стр.115] [стр.116] [стр.117] [стр.118] [стр.119] [стр.120] [стр.121] [стр.122] [стр.123] [стр.124] [стр.125] [стр.126] [стр.127] [стр.128] [стр.129] [стр.130] [стр.131] [стр.132] [стр.133] [стр.134] [стр.135] [стр.136] [стр.137] [стр.138] [стр.139] [стр.140] [стр.141] [стр.142] [стр.143] [стр.144] [стр.145] [стр.146] [стр.147] [стр.148] [стр.149] [стр.150] [стр.151] [стр.152] [стр.153] [стр.154] [стр.155] [стр.156] [стр.157] [стр.158] [стр.159] [стр.160] [стр.161] [стр.162] [стр.163] [стр.164] [стр.165] [стр.166] [стр.167] [стр.168] [стр.169] [стр.170] [стр.171] [стр.172] [стр.173] [стр.174] [стр.175] [стр.176] [стр.177] [стр.178] [стр.179] [стр.180] [стр.181] [стр.182] [стр.183] [стр.184] [стр.185] [стр.186] [стр.187] [стр.188] [стр.189] [стр.190] [стр.191] [стр.192] [стр.193] [стр.194] [стр.195] [стр.196]