Русский
Русский
English
Статистика
Реклама

Пролог

Роль логического программирования, и стоит ли планировать его изучение на 2021-й

22.12.2020 00:22:49 | Автор: admin

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

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

"Мда" - думаете Вы, и этим все сказано. Сложно! И тут наш отважный герой должен бы был перейти по второй ссылке, но я позволю себе сделать небольшую вставку, описав главное действующее лицо: Вы, по моей задумке, новичок в программировании, а даже если и нет, то точно не знакомы с логическим его обличием. Если же читатель уже несколько (или даже много) искушен знаниями в этой области, то рекомендую прочитать статью Что такое логическое программирование и зачем оно нам нужно, раз уж в вас горит интерес и любопытство к теме, а изучение материала ниже оставьте менее опытным коллегам.

Итак, пришло время второй ссылки. Что это будет? Статья на Хабре? Может быть статья на ином ресурсе? Прочитав пару первых абзацев на разных сайтах, вы, скорее всего, мало что поймете, так как, во-первых, материал обычно ориентирован на знающего читателя, во-вторых, хорошей и понятной информации по теме не так много в русскоязычном интернете, в-третьих, там почему-то постоянно речь идёт о некоем "прологе" (речь о языке программирования Prolog, разумеется), но сам язык, кажется, использует мало кто (почётное 35 место в рейтинге TIOBE). Однако наш герой не теряет мотивации и, спустя некоторое время, натыкается на эту самую статью, желая, все-таки понять:

  • Что такое логическое программирование

  • Какова история его создания и фундаментальные основы (серьезно, какому новичку это может быть интересно?)

  • Зачем и где его применяют

  • Стоит ли лично вам его изучать

Что ж, постараюсь ответить просто и понятно, обходя страшные термины и не вспоминая исторических личностей.

Что такое логическое программирование

В школе на уроках информатики многие, если не все, слышали про Pascal (а кто-то даже писал на нем). Многие также могли слышать про Python, C/C++/C#, Java. Обычно программирование начинают изучать именно с языков из этого набора, поэтому все привыкли, что программа выглядит как-то так:

НачатьКоманда1Команда2Если УСЛОВИЕ  Команда3Иначе  Команда4Закончить

Этот яркий, но малоинформативный пример призван показать, что обычно команды выполняются одна за другой, причем мы ожидаем, что следующие инструкции (команды) могут использовать результат работы предыдущих. Также мы можем описывать собственные команды, код которых будет написан подобным образом. Остановимся на том, что как завещал Фон Нейман, так компьютер и работает. Машина глупа, она делает то, что скажет программист, по четкому алгоритму, последовательно выполняя инструкции. Не может же, в конце концов, компьютер, подобно мыслящему живому существу, делать выводы, строить ассоциативные ряды Или может?

Давайте устроимся поудобнее рядом со своим компьютером и порассуждаем о жизни и смерти вместе с Аристотелем:

Всякий человек смертен.

Сократ - человек.

Следовательно, Сократ смертен.

Звучит логично. Но есть ли способ научить компьютер делать выводы как Аристотель? Конечно! И вот тут мы вспомним о Prolog-e, который так часто мелькает при поиске информации о логическом программировании. Как несложно догадаться, Prolog (Пролог) является самым популярным чисто логическим языком программирования. Давайте рассуждения об этом языке оставим на следующие разделы статьи, а пока что продемонстрируем "фишки" логических языков, используя Пролог.

Напишем небольшую программу, где перечислим, кто является людьми (ограничимся тремя) и добавим правило "всякий человек смертен":

% Всё, что после знака процента в строке - комментарииhuman('Plato'). % Платон - человекhuman('Socrates'). % Сократ - тоже человекhuman('Aristotle'). % Конечно, человеком был и Аристотель% ...и др. философыmortal(X) :- human(X). % Читаем так: "X смертен, если X - человек"

Что ж, давайте спросим у компьютера, смертен ли Сократ:

?- mortal('Socrates').true.

Компьютер выдал нам сухое "true", но мы, конечно, вне себя от счастья, так как вот-вот получим премию за успешное прохождение нашим умным устройством теста Тьюринга.

Так, теперь стоит успокоиться и разобраться, что же произошло. Вначале мы записали т. н. факты, то есть знания нашей программы о мире. В нашем случае ей известно лишь то, что Платон, Сократ и Аристотель - люди. Но что за странная запись "human('Socrates')." и почему это выглядит как функция? На самом деле "human" и "mortal" - предикаты от одной переменной. Да, тут уже пошли термины, но постараюсь объяснять их просто и понятно для тех, кто привык к императивному нормальному программированию.

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

% слова с большой буквы Prolog считает переменными, поэтому их следует заключать в кавычкиlike('Petya', 'Milk'). % программа знает, что Петя любит молокоgood('Kesha'). % Кеша хорошийnumber_of_sides('Triangle', 3). % у треугольника три вершиныlike('Misha', X). % не является фактом, так как значение переменной X не определено

Помимо фактов в логической программе присутствуют правила вывода. В данном случае это "mortal(X) :- human(X).". Набор правил вывода - это знания нашей программы о том, как выводить (искать/подбирать) решение. Правила записываются следующим образом:

a(X,Y,Z) :- b(X), c(Y,Z), d().

Предикат a от трех аргументов вернет истину, если удастся доказать истинность предикатов b, c и d. Читаются правила справа налево следующим образом: "Если b от X истинно И c от X, Y истинно И d истинно, то a от X, Y, Z истинно".

Уже на таком небольшом примере видно, что мы не описываем четко последовательность действий, приводящую к нужному результату. Мы описываем необходимые условия, при выполнении которых результат будет достигнут. Тут будет к слову упомянуть, что раз компьютер сам для нас выводит способ достижения результата на основе известных правил, то и использовать один и тот же код можно по-разному:

% Опишем набор фактов о том, кто что обычно ест на завтрак в семье Петиeat(father, cheese).eat(father, apple).eat(father, melon).eat(mother, meat).eat(sister, meat).eat('Petya', cheese).eat(brother, orange).

Теперь начнём делать запросы к программе (всё те же предикаты):

?- eat(father, apple). % ест ли отец яблокиtrue.?- eat(father, meat).  % ест ли отец мясоfalse.?- eat(sister, X). % что ест сестраX = meat.?- eat(X, cheese). % кто ест сырX = father ;X = 'Petya'.?- eat(X, Y). % кто что естX = father,Y = cheese ;X = father,Y = apple ;X = father,Y = melon ;X = mother,Y = meat ;X = sister,Y = meat ;X = 'Petya',Y = cheese ;X = brother,Y = orange.

Как видите, очень удобно. Стало быть, первым и очевидным применением логического программирования (об эффективности поговорим ниже) является работа с базами данных. Мы можем достаточно естественным образом описывать запросы, комбинируя предикаты, причем научить писать такие запросы можно даже человека, совершенно не знакомого с логическим программированием.

Какие задачи и как можно решать с помощью логического программирования

Давайте рассмотрим ряд учебных примеров (без подробного описания, все же статья обзорная) и подумаем, как те или иные подходы можно применять в реальной жизни. Начну с примера, призванного продемонстрировать, почему логическое программирование удобно, и за что же его любят математики. А именно, опишем правила вычисления производной:

d(X,X,1) :- !. % производная X по X = 1d(T,X,0) :- atomic(T). % производная константы = 0d(U+V,X,DU+DV) :- d(U,X,DU), d(V,X,DV). % производная суммы = сумме производныхd(U-V,X,DU-DV) :- d(U,X,DU), d(V,X,DV). d(-T,X,-R) :- d(T,X,R).d(C*U,X,C*W) :- atomic(C), C\=X, !, d(U,X,W). % производная константы, умноженной на выражение = константе на производную от выраженияd(U*V,X,Vd*U+Ud*V) :- d(U,X,Ud), d(V,X,Vd). % производная произведенияd(U/V,X,(Ud*V-Vd*U)/(V*V)) :- d(U,X,Ud), d(V,X,Vd). 

Запустим:

?- d((x-1)/(x+1),x,R).   R =  ((1-0)*(x+1)-(1+0)*(x-1))/((x+1)*(x+1)).

Пусть производная получилась довольно громоздкой, но мы и не ставили цель её упростить. Главное, из примера видно, что правила вывода производной на Prolog-е описываются очень близким образом к их математическому представлению. Чтобы сделать подобное на привычных языках программирования, пришлось бы вводить понятие дерева выражений, описывать каждое правило в виде функции и т. д. Тут же мы обошлись 8-ю строками. Но здесь важно остановиться и задуматься: компьютер не начал работать как-то иначе, он все ещё обрабатывает последовательности команд. Стало быть, те самые деревья, которые где-то все-таки должны быть зашиты, чтобы программа работала, действительно присутствуют, но в неявном виде. Деревья эти именуют "деревьями вывода", именно они позволяют подбирать нужные значения переменных, перебирая все возможные варианты их значений (существует механизм отсечения, который является надстройкой над логической основой языка, но не будем об этом).

Давайте на простом примере рассмотрим, что из себя представляет перебор, а затем то, чем он может быть опасен.

speciality(X,tech_translator) :- studied_languages(X), studied_technical(X). % X - технический переводчик, если изучал языки и технические предметыspeciality(X,programmer) :- studied(X,mathematics), studied(X, compscience). % X - программист, если изучал математику и компьютерные наукиspeciality(X,lit_translator) :- studied_languages(X), studied(X,literature). % X - литературный переводчик, если изучал языкиstudied_technical(X) :- studied(X,mathematics). % X изучал технические предметы, если изучал математикуstudied_technical(X) :- studied(X,compscience). % ...или компьютерные наукиstudied_languages(X) :- studied(X,english). % X изучал языки, если изучал английскийstudied_languages(X) :- studied(X,german). % ...или немецкийstudied(petya,mathematics). % Петя изучал математикуstudied(petya,compscience). % ...компьютерные наукиstudied(petya,english). % ...и английскиstudied(vasya,german). % Вася изучал немецкийstudied(vasya,literature). %...и литературу

Спросим, кто из ребят, известных компьютеру - технический переводчик:

?- speciality(X,tech_translator).X = petya ;X = petya ;false.

Агато есть Петя, Петя и ложь Что-то не так, подумает программист и попробует разобраться. На самом деле, перебирая все варианты значений X, Пролог пройдёт по такому дереву:

Дерево будет обходиться в глубину, то есть сначала рассматривается всё левое поддерево для каждой вершины, затем правое. Таким образом, Пролог дважды докажет, что Петя - технический переводчик, но больше решений не найдёт и вернёт false. Стало быть, половина дерева нам, в общем-то, была не нужна. В данном случае, перебор не выглядит особенно страшным, всего-то обработали лишнюю запись в базе. Чтобы показать "опасность" перебора, рассмотрим другой пример:

Представим, что перед нами в ячейках расположены три чёрных и три белых шара (как на картинке выше), которые требуется поменять местами. За один ход шар может или передвинуться в соседнюю пустую клетку, или в пустую клетку за соседним шаром ("перепрыгнуть" его). Решать будем поиском в ширину в пространстве состояний (состоянием будем считать расположение шаров в ячейках). Суть этого метода заключается в том, что мы ищем все пути длины 1, затем все их продления, затем продления продлений и т. д., пока не найдем целевую вершину (состояние). Почему поиск в ширину? Он первым делом выведет самый оптимальный путь, то есть самый короткий. Как может выглядеть код решения:

% Обозначения: w - белый шар, b - чёрный, e - пустая ячейкаis_ball(w). % w - шарis_ball(b). % b - шарnear([X,e|T],[e,X|T]) :- is_ball(X). % если фишка рядом с пустой ячейкой, то можно переместитьсяnear([e,X|T],[X,e|T]) :- is_ball(X).jump([X,Y,e|T],[e,Y,X|T]) :- is_ball(X), is_ball(Y). % если за соседним шаром есть пустая ячейка, то можно переместитьсяjump([e,Y,X|T],[X,Y,e|T]) :- is_ball(X), is_ball(Y).% предикат перемещения. Мы или рассматриваем первые элементы списка, или убираем первый элемент и повторяем операциюmove(L1,L2) :- near(L1,L2). move(L1,L2) :- jump(L1,L2).move([X|T1],[X|T2]) :- move(T1,T2).% предикат продления текущего пути. Если из состояния X можно перейти в состояние Y и% Y не содержится в текущем пути, то Y - удачное продлениеprolong([X|T],[Y,X|T]) :- move(X,Y), not(member(Y,[X|T])).% Первый аргумент - очередь путей, второй - целевое состояние, третий - результат, то есть найденный путьbdth([[X|T]|_],X,R) :- reverse([X|T], R). % Поиск в ширину нашел решение, если первый элемент пути совпадает с целью (путь наращивается с начала, так что перевернем результат)bdth([P|QI],Y,R) :- bagof(Z,prolong(P,Z),T), append(QI,T,QO), !, bdth(QO,Y,R). % Ищем все возможные продления первого пути и кладём в очередь, рекурсивно запускаем поискbdth([_|T],Y,R) :- bdth(T,Y,R). % Если продлений на предыдущем шаге не нашлось, то есть bagof вернул false, убираем первый путь из очередиbsearch(X,Y,R) :- bdth([[X]],Y,R). % Удобная обёртка над предикатом bdth% Предикат, который решает нашу задачу и выводит результат и длину найденного пути на экранsolve :- bsearch([w,w,w,e,b,b,b],[b,b,b,e,w,w,w],P), write(P), nl, length(P, Len), write(Len), nl.

Если вы попытаетесь вызвать предикат solve, то, в лучшем случае увидите ошибку, в худшем - среда зависнет. Дерево здесь (с учётом лежащих в памяти путей) настолько велико, что переполнит стек, так и не подарив нам ответа. Ну и что - скажете вы, это же может происходить и в императивных (процедурных (обычных)) языках программирования. Конечно. Но, повторюсь, на решение той же задачи на Питоне или Си (без использования библиотек) ушло бы на порядки больше времени. Давайте для полноты картины я приведу решение данной проблемы, а после перейдем к тому, какие же задачи решаются подобным образом.

Со стороны улучшения алгоритма можно предложить использовать поиск в глубину. Но как же, он ведь не даст оптимального результата? Сделаем просто: ограничим глубину поиска. Так мы точно не забьём стек и, возможно, получим ответ. Поступим так: проверим, есть ли пути длины 1, затем длины 2, затем длины 4 и т. д. Получим так называемый поиск с итерационным заглублением:

% Первый аргумент - текущий путь, второй - целевое состояние, третий - результат, то есть найденный путьdpth_id([X|T],X,R,0) :- reverse([X|T], R). % Успешное окончание поискаdpth_id(P,Y,R,N) :- N > 0, prolong(P,P1), N1 is N - 1, dpth_id(P1,Y,R,N1). % Если счётчик >0, то уменьшаем его и продолжаем поиск рекурсивноgenerator(1). % Изначально предикат вернет 1generator(N) :- generator(M), N is M + 1. % Рекурсивно получаем 2, 3, 4 и т. д.isearch(X,Y,R) :- generator(D), dpth_id([X],Y,R,D). % Удобная обертка, которая будет вызывать поиск от каждого натурального значения глубины.

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

С другой стороны, задачу можно было бы решить, не меняя код поиска, а лишь изменив правила перемещения шаров. Обратим внимание, что нам заранее известны входные и выходные данные. Приглядевшись становится понятно, что нет никакого смысла в движении фишек "назад". Действительно, если чёрным нужно встать в правые позиции, то какой смысл делать ходы влево? Перепишем предикаты движения:

near([w,e|T],[e,w|T]).near([e,b|T],[b,e|T]).jump([w,X,e|T],[e,X,w|T]) :- is_ball(X).jump([e,X,b|T],[b,X,e|T]) :- is_ball(X).

Хм, код стал даже проще. Запустив мы убедимся, что поиск (оба варианта), во-первых, работает, во-вторых, работает быстро, в-третьих, работает быстро и выводит результат. Это успех. Мало того, что мы решили задачку, только что был создан самый настоящий искусственный интеллект. Программа получает входные данные и желаемый результат, а затем сама ищет, как его достигнуть. Да, это однозначно успех.

Зачем и где применяют логическое программирование

Давайте вернемся к рассмотренным примерам и попробуем представить, как это можно использовать на практике.

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

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

  • Мета-программирование: С помощью того же Пролога можно описать свой собственный язык, соответственно, со своими правилами. Это достаточно важный момент для многих проектов, где сталкиваются специалисты разных сфер. Например, стоит задача анализа неких химических данных. В команде работают химик и программист. Получается, программист должен постоянно советоваться с химиком о том, что и как ему делать, ведь химическим формулам то в IT не учат. А разговаривать - это, в общем-то, не особенно приятно, особенно если кто-то умнее тебя. И тут программисту было бы очень удобно написать простой язык, на котором химик сможет сам записать свои формулы, не трогая разработчика. Вот тут и пригодится логическое программирование.

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

Стоит ли планировать его изучение на 2021-й

Тут оставлю своё субъективное мнение, разделённое на две части:

  • Если вы только-только делаете первые шаги в программировании, то браться за логическое программирование не стоит, поскольку на практике вы будете практически всегда писать код, используя императивные языки, и их изучение - основа вашей будущей специальности. Учиться разрабатывать программы сразу и в императивной, и в декларативной (описываем задачу и результат, а не как его получить) парадигмах, как по мне, малоэффективно.

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

И здесь остаётся лишь пожелать продуктивного 2021-го года!

Подробнее..

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

09.01.2021 22:17:27 | Автор: admin
Некоторое время назад я писал про Интернациональное программирование на естественных языках, в которой попытался представить достойную цель для абстрактного язык программирования, попробовав примерить на него роль связующего звена между миром программистов с компьютерами и не программистов.

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

И пользователи, как программисты, так и не программисты, просто хотя решать возникающие перед ними задачи. И хотя задачи бывают совершенно разные, но если способ (алгоритм) её решения известен, то выбрать язык для её решения не составит никакого труда.

За исключением одного класса задач. Задач, решение которых которых нельзя описать в виде алгоритма. Но можно указать некие критерии, которым должно удовлетворять искомое решение. Я имею ввиду логические языки программирования и Пролог, как самый яркий представитель этого класса.

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

Так почему этого так и не случилось? В чем проблема Пролога, да и любой системы / языка программирования, назначение которых анализировать факты и искать ответы на вопросы?

Эта проблема называется Комбинаторный взрыв экспоненциальная зависимость времени работы алгоритма от количества входных данных. И есть как минимум два решения этой проблемы.



Подходы к написания программ


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

Императивный это классический вариант написания программы, при котором программист сам задает шаги алгоритма работы для получения конечного результата. А сам текст программы состоит из последовательности команд, которые читаю, сохраняют и обрабатывают данные или вызывают другие команды.

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

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

И проблема под называнием Комбинаторный взрыв сильнее всего оказывает негативное влияние как раз на подобные языки. Ведь в императивном подходе программист сам отвечает за последовательность выполняемых команд, и если он запрограммировал алгоритм полного перебора всех возможных вариантов решений, то он сам себе злобный Буратино.

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

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

Именно в этом случае и начинается экспоненциальный рост времени выполнения алгоритма. И начиная с определенного порога, время ожидания ответа становится неприемлемым для реального использования. Это и означает Комбинаторный взрыв, резкий (взрывной) рост времени выполнения алгоритма при увеличении размера входных данных.

Проблема поиска решений


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

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

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

Масштабирование производительности


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

Другое дело горизонтальное масштабирование, при котором выполнение алгоритма запускается на отдельных узлах, которые параллельно решают одну и ту же задачу. Такой способ масштабирования позволяет уже значительно сократить время получения итогового результата для сложных вычислительных задач. И хотя это способ является решением в лоб, но успехи в области data science доказывают успешность такого подхода.

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

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

Поиск обобщенного решения


Вторым способом решения решения проблемы комбинаторного взрыва является уменьшение вычислительной сложности решения. Тут имеется ввиду не выбор другого алгоритма или решение задачи в символьном виде. Если такое возможно, то все опять сразу сведется к императивному стилю программированию.

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

Это возможно как алгоритмическими методами (откат и отсечение в Прологе), так и с применением машинного обучения, которое очень хорошо справляется с поиском различных закономерностей.

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

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

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

Область не решаемых задач


Как вы считает, а реально ли создать язык логического программирования, который бы сам умел автоматизировать поиск решений для задач подобных классов? Или хотя бы имел в своем арсенале встроенные механизмы для автоматизации подобной деятельности?
Подробнее..

Ограничение прав доступа к переменным

20.01.2021 20:22:57 | Автор: admin

Конец восьмидесятых. Всего два года я отсутствовал на родном предприятии, а меня встретил уже меняющийся компьютерный мир. В отделах стали появляться персоналки: у кого IBM-PC/XT, у кого Правец, а у кого ЕС-1840. Число пользователей БЭСМ-6 и даже ЕС и СМ-4 стало асимптотически приближаться к нулю. На фоне новых возможностей все их фишки сразу побледнели. Например, смешно, что еще недавно какая-нибудь замена терминала VT-340 на VT-52100 c памятью на 5 страниц, позволяющей вводить текст еще до включения БЭСМ, казалась важной.

Расстаться со старыми заделами и навыками психологически мне было даже проще, чем многим, поскольку после двухлетнего отсутствия я вернулся на работу уже в другой отдел и, так сказать, сразу отрекся от старого мира и отряхнул его прах со своих ног.

Впрочем, последние годы работа с БЭСМ-6 через диалоговую программу Пульт разработки МГУ, как раз очень напоминала работу за первыми персоналками и поэтому переход был несложным.

А вот задачи стали другие. Отдел занимался разработкой ПО системы управления Энергия-Буран. Точнее, отдел занимался комплексацией, верификацией, взаимодействием с наземным ПО и т.п., а собственно разработкой занималось сразу несколько отделов. Я впервые принимал участие в проекте, где были заняты десятки программистов. Язык программирования ПРОЛ-2 разработки ИПМ АН СССР.

Вообще-то, девичья фамилия этого языка была Пролог-Ц от ПРОграммирования ЛОГики. А литера Ц - это, вероятно, ЦУП. Но поскольку в то время на слуху был японский Пролог с его транспьютерами, вероятно разработчикам надоело отвечать на вопросы о применении транспьютеров в Буране, поэтому вторая версия языка вышла под таким скромным и безликим именем.

Язык был специфический, для задач управления. Типичный алгоритм выглядел так: выдать такую-то команду, подождать 0.3 миллисекунды, проверить такую-то переменную. Если она нулевая выдать другую команду и запустить такой-то процесс. И все в таком духе.

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

Сказано-сделано. Часть транслятора была написана на ассемблере и поэтому летал он ласточкой.

В то время было модно извлекать из внутреннего динамика персоналки разные звуки. Ребята из моего бывшего отдела даже спаяли простой АЦП на параллельный разъем и через микрофон от древней Яузы-5 мы записали ряд сигналов и фраз.

Например, при отсутствии замечаний проверочный транслятор бесцветным голосом произносил (больше подходит: сипел) половинку подходящего к случаю т.н. заходеризма (афоризма Б. Заходера): если взглянуть формально, все обстоит нормально.

А описываемая история произошла в момент, когда в почти торжественной обстановке состоялся первый прогон через проверочный транслятор множества уже готовых и проверенных модулей.

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

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

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

После выяснения этого факта даже было совещание. Не столько на тему помарки в трансляторе, сколько на тему философии и технологии разработки. Ведь рассчитывали на что? Что разработчики проанализируют область действия каждой переменной и явно и осмысленно укажут множество объектов, которые могут эту переменную читать/писать.

А что получилось в реальности? Разработчики чихали на анализ и совали свои модули в транслятор. Ругнулся он по поводу прав так и быть допишем список, не ругнулся и так сойдет. И при этом все работало нормально и казалось, что защита от порчи чужих данных хорошо обеспечена.

Лично я не считаю допустивших это программистов какими-то балбесами, не соблюдающими элементарные правила. Все силы и внимание уходили на разработку самих алгоритмов, отработку их взаимодействия на стенде, анализ парирования нештатных ситуаций и т.п. А на правила записи уже не хватало ни внимания, ни желания.

А что, разве при отработке не было случаев, когда один процесс портил переменную другому процессу? Конечно, были. Только во всех этих случаях как раз с правами доступа все было хорошо.

С тех пор прошло уже десятка три лет. Программирование и теоретически и практически ушло далеко вперед и добилось больших успехов.

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

В духе так и не озвученной нами в трансляторе второй половины афоризма Бориса Заходера.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru