Языки логического программирования

Язык Пролог: вычисления и процедурная семантика Пролога

Разбить на страницы
Показывать лекцию целиком

Вычисления и процедурная семантика Пролога

Итак, выполнение программы Пролога заключается в интерпретации значений переменных, при которой все правила становятся истинными. Если такой интерпретации нет, то ответ Нет. Если правила программы истинны при любой интерпретации, то ответ Да. В остальных случаях ответами являются все всевозможные подстановки переменных, при которых все правила истинны.

Каждую формулу Пролога (запрос, факт, правило) можно рассматривать как процедуру . При этом левая часть правила - вход в процедуру , а правая часть - тело процедуры , которая может содержать вызовы других процедур. Атомарные предикаты запросов можно также понимать как вызовы процедур .

Мы будем описывать алгоритм вычислений, одновременно рассматривая и поясняя его на примере.

Пример 1. Рассмотрим запрос

<- предок (X, петр)

для программы предыдущего раздела.

1. В вычислениях каждый атом правой части запроса рассматривается как вызов процедуры. Если в запросе несколько атомов, то эти атомы рассматриваются по очереди, и если переменные очередного атома не имеют ни одной интерпретации, ведущей к ответу для этой части запроса, то и весь исходный запрос не имеет ответа для интерпретации переменных, полученной в вычислениях предыдущих атомов. Если же есть интерпретации для переменных атома, ведущие к ответу, то только такие интерпретации переменных используются в частях запроса для следующих атомов. В данном запросе 1 вызов процедуры предок.

2. При вызове процедуры мы по очереди рассматриваем формулы (правила или факты) этой процедуры.

Шаг 1. В данном примере для процедуры предок мы берем сначала первое правило для этой процедуры, но делаем регулярную замену переменных:

предок (X1, Y1) <- мать (X1, Y1).

Вызов процедуры предок ( X, петр ) совпадает с заголовком этого правила при интерпретации: X=X1, Y1= петр.

3. Производим интерпретацию переменных заголовка (правила или левой части факта) и заменяем прежний атомарный запрос на запрос тела процедуры, в котором произведена полученная интерпретация переменных.

4. Если интерпретация невозможна, то необходимо перейти к предыдущей точке выбора формулы процедуры. Если при этом нет предыдущей точки выбора, то вычисления закончились неуспешно.

В примере получаем запрос

<- мать (X1, петр).

Шаг 2. С этим запросом поступаем аналогичным образом, но так как процедура мать представляет собой множество фактов, то рассматриваем лишь такие факты

мать(X2, Y2) <-,

которые допускают интерпретацию Y2 = петр. Так как таких нет, то вычисление закончилось неудачно и нужно вернуться к предыдущей точке алгоритма, где мы можем сделать следующий выбор. Такой точкой является шаг 1, где мы выбирали правило процедуры предок.

Шаг 3. Выбираем следующее правило процедуры \unde предок с заменой переменных:

предок (X1, Y1) <- отец (X1, Y1).

Вызов процедуры предок ( X, петр ) совпадает с заголовком этого правила при интерпретации: X1=X, Y1 = петр. Заменяем исходный запрос на тело процедуры с полученной интерпретацией переменных и получаем новый запрос:

<- отец (X1, петр).

Шаг 4. С этим запросом поступаем аналогичным образом, но так как процедура отец представляет собой множество фактов, то рассматриваем лишь такие факты

отец (X2, Y2)<-,

которые допускают интерпретацию X2 = X1, Y2 = петр. Есть 1 такой факт

отец (иван, петр) <-,

который дает интерпретацию X2=иван.

5. Как только замена заголовка процедуры на тело дает пустой запрос, который является истинным, вычисления исходного запроса (атома) заканчиваются успешно.

Делая обратные подстановки иван = X2 = X1 = X, получаем первый ответ X=иван.

Теперь необходимо вернуться к предыдущей точке выбора формулы.

Шаг 5. В процедуре предок выбираем следующее правило с заменой переменных:

предок (X1, Y1) <- предок (Z1, Y1), мать (X1, Z1).

Вызов процедуры предок (X, петр) совпадает с заголовком этого правила при интерпретации: X1=X, Y1= петр. Заменяем исходный запрос на тело процедуры с полученной интерпретацией переменных и получаем новый запрос:

<- предок (Z1,петр), мать (X1, Z1).

Мы получили запрос с 2 атомами и должны рассматривать их по очереди.

Шаг 6. Рассматриваем запрос с первым атомом

<- предок (Z1,петр) В процедуре предок выбираем первое правило (первое, потому что это новый запрос в нашем вычислении) с заменой переменных:

предок (X1, Y1) <- мать (X1, Y1).

Вызов процедуры предок (X, петр) совпадает с заголовком этого правила при интерпретации: Z1=X1,Y1=петр. Делая замену на тело этой процедуры (с интерпретацией переменных), получаем запрос:

<- мать (X1, петр)

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

предок (X1, Y1) <- отец (X1, Y1).

Вызов процедуры предок (X, петр) совпадает с заголовком этого правила при интерпретации: X1=Z1,Y1=петр. Заменяем исходный запрос на тело процедуры с полученной интерпретацией переменных и получаем новый запрос:

<- отец (X1, петр).

Шаг 7. С этим запросом поступаем аналогичным образом, но так как процедура отец представляет собой множество фактов, то рассматриваем лишь такие факты

отец (X2, Y2) <-,

которые допускают интерпретацию X2=X1, Y2=петр. Есть 1 такой факт

отец (иван, петр) <-,

который дает интерпретацию X2 = иван. Вычисления запроса для первого атома шага 5 закончились успешно, и мы получаем интерпретацию Z1 = иван, которую используем во втором атоме запроса, рассматриваемом на следующем шаге.

Шаг 8. Рассматриваем второй атом запроса с интерпретацией переменной Z1:

<- мать (X1, иван).

Так как процедура мать представляет собой множество фактов, то рассматриваем лишь такие факты

мать(X2, Y2) <-,

которые допускают интерпретацию X2 = X1, Y2 = иван. Есть 1 такой факт

мать (елена, иван) <-,

который дает интерпретацию X2 = елена. Так как это факт, то вычисления второго атома запроса также закончились успешно. В результате получаем елена = X2=X1=X, и, следовательно, второй ответ X = елена.

Теперь для завершения вычислений нужно вернуться к предыдущей точке выбора и рассмотреть последнее правило процедуры предок:

предок (X1, Y1) <- предок (Z1, Y1), отец (X1, Z1).

Мы опять получили запрос с 2 атомами и должны рассматривать их по очереди.

Шаг 9. Рассматриваем запрос с первым атомом

<- предок (Z1,петр).

Он аналогичен запросу, рассмотренному на шаге 6, и потому его вычисление приводит к правилу

предок (X1, Y1) <- отец (X1, Y1).

Вызов процедуры предок (X, петр) совпадает с заголовком этого правила при интерпретации: X1=Z1,Y1=петр. Заменяем исходный запрос на тело процедуры с полученной интерпретацией переменных и получаем запрос:

<- отец (X1, петр).

С этим запросом поступаем способом, аналогичным шагу 7, где используется факт

отец (иван, петр) <-,

который дает интерпретацию X2=иван. Вычисления запроса для первого атома шага 9 закончились успешно, и мы получаем интерпретацию Z1=иван, которую используем во втором атоме запроса, рассматриваемом на следующем шаге.

Шаг 10. Рассматриваем второй атом запроса с интерпретацией переменной Z1:

<- отец (X1, иван).

Так как процедура отец представляет собой множество фактов, то рассматриваем лишь такие факты

отец (X2, Y2)<-,

которые допускают интерпретацию Y2=иван. Таких фактов нет, эти вычисления закончились неудачно. Поскольку мы перебрали все формулы процедуры предок, то вычисления закончены. Окончательно получаем множество ответов:

X={иван, елена}.

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

Пример 2. Рассмотрим правило App склеивания списков (от append - присоединение к списку). Оно выражается следующими фактом и правилом:

App(nil; X; X) <-
App(U:X; Y; U:Z) <- App(X; Y; Z),

которые означают, что

  • результатом склеивания списка X с пустым списком является список X ;
  • 2) если результат склеивания списков X и Y есть список Z, то результат склеивания списков u.X и Y есть список U.Z.
  • Рассмотрим выполнение запроса

    <- App (a:b:nil; b:nil; X),

    при котором мы ожидаем получить X=a.b.b.nil.

    Шаг 1. Пытаемся применить первое правило (факт)

    App (nil, X1, X1) <-.

    При отождествлении деревьев факта и запроса (см. рис. 8.1) получается недопустимая подстановка nil=a.b.nil. Поэтому отождествление невозможно, и этот шаг закончился неудачно.

    (рис 8.1)

    Шаг 2. Пытаемся применить второе правило

    App (U1.X1, Y1, U1.Z1) <- App (X1, Y1, Z1).

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

    App (b.nil, b.nil, Z1) <-.

    (рис 8.2)

    Шаг 3. Структура запроса определена деревом на рис. 8.3. Попытка применить правило 1 не дает результата, так как $$nil \ne b.nil$$.

    (рис 8.3)

    Шаг 4. Пытаемся применить правило 2:

    App (U2.X2, Y1, U2.Z2) <- App (X2, Y2, Z2).

    Отождествление запроса и левой части правила возможны только при интерпретации, показанной на рис. 8.3. Делая подстановку в запрос и заменяя на правую часть правила, получаем запрос:

    <- App (nil, b.nil, Z2).

    Структура его дерева показана на рис. 8.4.

    (рис 8.4)

    Шаг 5. Пытаемся применить правило 1:

    App (nil, X3, X3)<-.

    Отождествление этого запроса и левой части правила возможны только при интерпретации, показанной на рис. 8.4. Делая подстановку и заменяя на тело правила, получаем пустой запрос, что свидетельствует о благополучном завершении интерпретаций. Подставляя значение Z2 в выражение для Z1, а затем значение Z1 - в выражение для X, получим X = a.Z1 = a.b.Z2 = a.b.b.nil, что и требовалось.

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

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

    В описанной нами семантике вычислений Пролога формально не описано, как же находится интерпретация переменных при отождествлении. Может быть несколько интерпретаций, а в рассматриваемых примерах - только по одной. Поэтому сначала точно опишем так называемый чистый недетерминированный Пролог, а затем перейдем уже к реализациям Пролога.

    Упражнения

  • Проведите для упражнения 3.6.1 пошаговые вычисления.
  • Проведите для упражнения 3.6.2 пошаговые вычисления.
  • Проведите для упражнения 3.6.3 пошаговые вычисления.
  • Проведите для упражнения 3.6.4 пошаговые вычисления.
  • Измените правила базы знаний из упражнения 3.6.1 так, чтобы можно было выполнить запрос, строящий цепочки включающих друг друга кодов, начиная от заданного программного кода.
  • Страницы:

    Вычисления и процедурная семантика Пролога

    Итак, выполнение программы Пролога заключается в интерпретации значений переменных, при которой все правила становятся истинными. Если такой интерпретации нет, то ответ Нет. Если правила программы истинны при любой интерпретации, то ответ Да. В остальных случаях ответами являются все всевозможные подстановки переменных, при которых все правила истинны.

    Каждую формулу Пролога (запрос, факт, правило) можно рассматривать как процедуру . При этом левая часть правила - вход в процедуру , а правая часть - тело процедуры , которая может содержать вызовы других процедур. Атомарные предикаты запросов можно также понимать как вызовы процедур .

    Мы будем описывать алгоритм вычислений, одновременно рассматривая и поясняя его на примере.

    Пример 1. Рассмотрим запрос

    <- предок (X, петр)

    для программы предыдущего раздела.

    1. В вычислениях каждый атом правой части запроса рассматривается как вызов процедуры. Если в запросе несколько атомов, то эти атомы рассматриваются по очереди, и если переменные очередного атома не имеют ни одной интерпретации, ведущей к ответу для этой части запроса, то и весь исходный запрос не имеет ответа для интерпретации переменных, полученной в вычислениях предыдущих атомов. Если же есть интерпретации для переменных атома, ведущие к ответу, то только такие интерпретации переменных используются в частях запроса для следующих атомов. В данном запросе 1 вызов процедуры предок.

    2. При вызове процедуры мы по очереди рассматриваем формулы (правила или факты) этой процедуры.

    Шаг 1. В данном примере для процедуры предок мы берем сначала первое правило для этой процедуры, но делаем регулярную замену переменных:

    предок (X1, Y1) <- мать (X1, Y1).

    Вызов процедуры предок ( X, петр ) совпадает с заголовком этого правила при интерпретации: X=X1, Y1= петр.

    3. Производим интерпретацию переменных заголовка (правила или левой части факта) и заменяем прежний атомарный запрос на запрос тела процедуры, в котором произведена полученная интерпретация переменных.

    4. Если интерпретация невозможна, то необходимо перейти к предыдущей точке выбора формулы процедуры. Если при этом нет предыдущей точки выбора, то вычисления закончились неуспешно.

    В примере получаем запрос

    <- мать (X1, петр).

    Шаг 2. С этим запросом поступаем аналогичным образом, но так как процедура мать представляет собой множество фактов, то рассматриваем лишь такие факты

    мать(X2, Y2) <-,

    которые допускают интерпретацию Y2 = петр. Так как таких нет, то вычисление закончилось неудачно и нужно вернуться к предыдущей точке алгоритма, где мы можем сделать следующий выбор. Такой точкой является шаг 1, где мы выбирали правило процедуры предок.

    Шаг 3. Выбираем следующее правило процедуры \unde предок с заменой переменных:

    предок (X1, Y1) <- отец (X1, Y1).

    Вызов процедуры предок ( X, петр ) совпадает с заголовком этого правила при интерпретации: X1=X, Y1 = петр. Заменяем исходный запрос на тело процедуры с полученной интерпретацией переменных и получаем новый запрос:

    <- отец (X1, петр).

    Шаг 4. С этим запросом поступаем аналогичным образом, но так как процедура отец представляет собой множество фактов, то рассматриваем лишь такие факты

    отец (X2, Y2)<-,

    которые допускают интерпретацию X2 = X1, Y2 = петр. Есть 1 такой факт

    отец (иван, петр) <-,

    который дает интерпретацию X2=иван.

    5. Как только замена заголовка процедуры на тело дает пустой запрос, который является истинным, вычисления исходного запроса (атома) заканчиваются успешно.

    Делая обратные подстановки иван = X2 = X1 = X, получаем первый ответ X=иван.

    Теперь необходимо вернуться к предыдущей точке выбора формулы.

    Шаг 5. В процедуре предок выбираем следующее правило с заменой переменных:

    предок (X1, Y1) <- предок (Z1, Y1), мать (X1, Z1).

    Вызов процедуры предок (X, петр) совпадает с заголовком этого правила при интерпретации: X1=X, Y1= петр. Заменяем исходный запрос на тело процедуры с полученной интерпретацией переменных и получаем новый запрос:

    <- предок (Z1,петр), мать (X1, Z1).

    Мы получили запрос с 2 атомами и должны рассматривать их по очереди.

    Шаг 6. Рассматриваем запрос с первым атомом

    <- предок (Z1,петр) В процедуре предок выбираем первое правило (первое, потому что это новый запрос в нашем вычислении) с заменой переменных:

    предок (X1, Y1) <- мать (X1, Y1).

    Вызов процедуры предок (X, петр) совпадает с заголовком этого правила при интерпретации: Z1=X1,Y1=петр. Делая замену на тело этой процедуры (с интерпретацией переменных), получаем запрос:

    <- мать (X1, петр)

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

    предок (X1, Y1) <- отец (X1, Y1).

    Вызов процедуры предок (X, петр) совпадает с заголовком этого правила при интерпретации: X1=Z1,Y1=петр. Заменяем исходный запрос на тело процедуры с полученной интерпретацией переменных и получаем новый запрос:

    <- отец (X1, петр).

    Шаг 7. С этим запросом поступаем аналогичным образом, но так как процедура отец представляет собой множество фактов, то рассматриваем лишь такие факты

    отец (X2, Y2) <-,

    которые допускают интерпретацию X2=X1, Y2=петр. Есть 1 такой факт

    отец (иван, петр) <-,

    который дает интерпретацию X2 = иван. Вычисления запроса для первого атома шага 5 закончились успешно, и мы получаем интерпретацию Z1 = иван, которую используем во втором атоме запроса, рассматриваемом на следующем шаге.

    Шаг 8. Рассматриваем второй атом запроса с интерпретацией переменной Z1:

    <- мать (X1, иван).

    Так как процедура мать представляет собой множество фактов, то рассматриваем лишь такие факты

    мать(X2, Y2) <-,

    которые допускают интерпретацию X2 = X1, Y2 = иван. Есть 1 такой факт

    мать (елена, иван) <-,

    который дает интерпретацию X2 = елена. Так как это факт, то вычисления второго атома запроса также закончились успешно. В результате получаем елена = X2=X1=X, и, следовательно, второй ответ X = елена.

    Теперь для завершения вычислений нужно вернуться к предыдущей точке выбора и рассмотреть последнее правило процедуры предок:

    предок (X1, Y1) <- предок (Z1, Y1), отец (X1, Z1).

    Мы опять получили запрос с 2 атомами и должны рассматривать их по очереди.

    Шаг 9. Рассматриваем запрос с первым атомом

    <- предок (Z1,петр).

    Он аналогичен запросу, рассмотренному на шаге 6, и потому его вычисление приводит к правилу

    предок (X1, Y1) <- отец (X1, Y1).

    Вызов процедуры предок (X, петр) совпадает с заголовком этого правила при интерпретации: X1=Z1,Y1=петр. Заменяем исходный запрос на тело процедуры с полученной интерпретацией переменных и получаем запрос:

    <- отец (X1, петр).

    С этим запросом поступаем способом, аналогичным шагу 7, где используется факт

    отец (иван, петр) <-,

    который дает интерпретацию X2=иван. Вычисления запроса для первого атома шага 9 закончились успешно, и мы получаем интерпретацию Z1=иван, которую используем во втором атоме запроса, рассматриваемом на следующем шаге.

    Шаг 10. Рассматриваем второй атом запроса с интерпретацией переменной Z1:

    <- отец (X1, иван).

    Так как процедура отец представляет собой множество фактов, то рассматриваем лишь такие факты

    отец (X2, Y2)<-,

    которые допускают интерпретацию Y2=иван. Таких фактов нет, эти вычисления закончились неудачно. Поскольку мы перебрали все формулы процедуры предок, то вычисления закончены. Окончательно получаем множество ответов:

    X={иван, елена}.

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

    Пример 2. Рассмотрим правило App склеивания списков (от append - присоединение к списку). Оно выражается следующими фактом и правилом:

    App(nil; X; X) <-
    App(U:X; Y; U:Z) <- App(X; Y; Z),

    которые означают, что

  • результатом склеивания списка X с пустым списком является список X ;
  • 2) если результат склеивания списков X и Y есть список Z, то результат склеивания списков u.X и Y есть список U.Z.
  • Рассмотрим выполнение запроса

    <- App (a:b:nil; b:nil; X),

    при котором мы ожидаем получить X=a.b.b.nil.

    Шаг 1. Пытаемся применить первое правило (факт)

    App (nil, X1, X1) <-.

    При отождествлении деревьев факта и запроса (см. рис. 8.1) получается недопустимая подстановка nil=a.b.nil. Поэтому отождествление невозможно, и этот шаг закончился неудачно.

    (рис 8.1)

    Шаг 2. Пытаемся применить второе правило

    App (U1.X1, Y1, U1.Z1) <- App (X1, Y1, Z1).

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

    App (b.nil, b.nil, Z1) <-.

    (рис 8.2)

    Шаг 3. Структура запроса определена деревом на рис. 8.3. Попытка применить правило 1 не дает результата, так как $$nil \ne b.nil$$.

    (рис 8.3)

    Шаг 4. Пытаемся применить правило 2:

    App (U2.X2, Y1, U2.Z2) <- App (X2, Y2, Z2).

    Отождествление запроса и левой части правила возможны только при интерпретации, показанной на рис. 8.3. Делая подстановку в запрос и заменяя на правую часть правила, получаем запрос:

    <- App (nil, b.nil, Z2).

    Структура его дерева показана на рис. 8.4.

    (рис 8.4)

    Шаг 5. Пытаемся применить правило 1:

    App (nil, X3, X3)<-.

    Отождествление этого запроса и левой части правила возможны только при интерпретации, показанной на рис. 8.4. Делая подстановку и заменяя на тело правила, получаем пустой запрос, что свидетельствует о благополучном завершении интерпретаций. Подставляя значение Z2 в выражение для Z1, а затем значение Z1 - в выражение для X, получим X = a.Z1 = a.b.Z2 = a.b.b.nil, что и требовалось.

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

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

    В описанной нами семантике вычислений Пролога формально не описано, как же находится интерпретация переменных при отождествлении. Может быть несколько интерпретаций, а в рассматриваемых примерах - только по одной. Поэтому сначала точно опишем так называемый чистый недетерминированный Пролог, а затем перейдем уже к реализациям Пролога.

    Упражнения

  • Проведите для упражнения 3.6.1 пошаговые вычисления.
  • Проведите для упражнения 3.6.2 пошаговые вычисления.
  • Проведите для упражнения 3.6.3 пошаговые вычисления.
  • Проведите для упражнения 3.6.4 пошаговые вычисления.
  • Измените правила базы знаний из упражнения 3.6.1 так, чтобы можно было выполнить запрос, строящий цепочки включающих друг друга кодов, начиная от заданного программного кода.
  • Вернуться к учебному плану