Привет всем. Публикую выдержки из вводного курса нашей компании по промышленному программированию. Если выдержки покажутся интересными хаброобществу, продолжу публиковать другие куски.
Курс этот предназначен прежде всего для junior developer'ов и позволяет повысить уровень аргументации в холиварах на тему «почему PHP (Java, Perl, Bash) отстой».
В данном курсе рассматривается поточная модель программирования, основанная на вычислительной машине Тьюринга, история возникновения современных ЯП, а так же область их применимости. А так же внятно и доступно объясняется что такое ООП и функциональное программирование.
Часть первая: Как появлялись языки и зачем
Часть вторая: Принцип сохранения функционала
Часть третья: Синтаксический сахар или история развития языков
Как появлялись языки и зачем
Язык нужен для обработки данных и представления результата обработки в той или иной форме.
У вычислительной машины всегда есть источник данных и сток данных. По большому счёту, компьютер просто переливает информацию из нескольких источников в несколько стоков, по пути производя преобразования.
Язык программирования — это язык, которым мы задаём правила преобразования информации в удобной для себя или для компьютера форме и ничего более. Фактически, все языки программирования можно свести к операция на машине Тьюринга.
Машина Тьюринга
Машина Тьюринга — это бесконечная магнитная лента (вспомните года, когда работал Тьюринг — магнитная лента тогда была очень прогрессивным устройством). Лента разбита на логические ячейки — последовательные отрезки ленты, на которые можно записать тот или иной сигнал (к примеру — ASCII символы).
Машина может сдвинуть ленту влево или вправо на одну ячейку, считать текущий отрезок или записать что-то в текущий отрезок. Каждая операция начинается с того, что машина считывает значение текущей ячейки, проверяет, что это значение значит по таблице переходов и осуществляет два действия: запись в эту же ячейку нового символа и сдвиг на одну ячейку влево или сдвиг на ячейку вправо.
После совершения этих двух действий цикл повторяется, между циклами данные не передаются. Машина в каждый конкретный цикл обрабатывает только тот символ, который она считала из текущей ячейки. Ещё раз — символ из ячейки, которую она считала в прошлый раз машина не помнит.
Программа на машине Тьюринга задаётся в виде таблицы переходов: если символ А — то сдвиг влево, если Б — сдвиг вправо, если С — записать в ячейку D и сдвинуться влево, если E — то остановить работу (и так далее, пока символы не кончатся).
Одно из главных определений, задаваемых машиной Тьюринга — это контекст исполнения:
Контекст выполнения программы — совокупность всех данных, определяющих её поведение.
Парадигмы задания правил
Как видно по примеру машины Тьюринга, ЯП манипулируют не данными, а преобразованиями данных. Но правила для обработки как-то надо представлять и определять. На данный момент найдено два способа задания правил для обработки — это императивная и декларативная формы записи правил.
Декларативная форма
Первая форма называется декларативной. Хороший пример этого типа описания преобразований является алгебраическая запись: a принадлежит отрезку [b,c] (постулируем этот факт), d принадлежит прямой (a,b), если выполняется условие d=f(a,b) (тоже постулируем этот факт).
Хорошо известными вам декларативными ЯП являются правила описания страницы HTML и CSS. В последнем, к примеру, может постулироваться, что если элемент входит в тот или иной класс, то необходимо покрасить его в красненький.
Декларативный ЯП описывает не шаги, которые нужно сделать, чтобы получить результат, а то, что требуется получить в качестве результата.
Декларативные ЯП уделяют особое внимание на входные данные и конечный результат работы программы. На процесс преобразования данных обращается меньшее внимание.
Императивная форма
Вторая форма представления правил называется императивной. Теоретической моделью для императивного программирования послужила машина Тьюринга.
Императивная форма: это список приказов, выражаемых в повелительном наклонении. К примеру, присвоить a значение (c-b)/2. Или если a равно b, то перейти к выполнению следующих команд иначе — других команд. Внимание в императивной форме отдаётся не самим данным, а процессу обработки этих данных.
Императивный язык заточен на описание процесса изменения информации, а не на результат преобразования.
Выполнение программы сводится к последовательному выполнению операторов с целью преобразования исходных данных в результаты.
Интересно, что правила для императивных языков задаются в декларативной форме: вот, мол, у нас есть такие правила переходов и обработки информации.
Парадигмы обработки информации
Кроме способа задания правил ЯП так же различаются по своему способу подхода к обрабатываемым данным. Если внимательно посмотреть на информацию, которую преобразуют ЯП, то можно увидеть, что она бывает двух типов: дискретная и непрерывная.
Дискретно-событийная модель
Классическим примером дискретного представления информации является та же машина Тьюринга. Вся информация, как подаваемая в неё, так и результат обработки поделена на равновеликие кусочки.
Ещё один пример дискретного представления — это обработка входящего потока цифровых аудио данных. Для чтения такого потока используется периодически наполняемый массив чисел — буфер данных, который потом подвергается обработке и преобразованиям.
Нетрудно заметить, что при дискретном представлении информации цикличность работы программы задаётся самим способом ввода: прочитали байт — сделали обработку — выплюнули результат.
Но что происходит, если кусочки информации не выравнены по своим размерам? К примеру, на вход программы подаётся длинный текст, а программа должна оперировать не буквами-символами, а словами?
Для решения подобных задач вводится понятие события. Событие возникает в ответ на какую-либо распознанную последовательность, прочитанную из источника данных.
Если рассматривать пример с текстом и словами, то мы байт за байтом читаем данные и записываем в пополняемый буфер памяти. И делаем это до тех пор, пока в потоке данных мы не читаем пробел или точку, что будет означать возникновение события “конец слова”.
После возникновения события “конец слова”, мы можем быть уверенны, что в буфере у нас оказывается слово целиком. Теперь мы можем его проанализировать, понять, что оно значит, применить заданные правила обработки, подготовить новый буфер для считывания следующего слова и т.д.
В дискретно-событийном подходе обработки информации функционирование системы представляется как хронологическая последовательность событий. Событие происходит в определенный момент времени и изменяет состояние системы.
Наступление события означает для программы одно очень важное действие: программа меняет своё собственное состояние (контекст выполнения). После наступления события программа может работать исходя из этого знания, что это событие произошло. К примеру, если мы нашли слово “абырвалг”, то теперь можем пользоваться знанием, что оно существует далее, при выполнении хода работ.
Модель непрерывных данных
Подход к программированию — “события случаются и мы должны реагировать на это” повсеместно распространён и кажется сейчас чем-то естественным, но это совсем не так. Многие вещи более естественно описывать в непрерывных данных.
К примеру, классическая школьная задача наполненности бассейном водою через заданные промежутки времени (бассейн, в который одна труба наливает воду, а другая — отводит) намного проще решается с помощью алгебраических декларативных языков, как разница решений функций подвода и отвода воды.
Так же, при использовании непрерывных данных существенно проще решаются задачи связанные с векторной графикой. К примеру, для отрисовки круга на экране с помощью дискретной обработки данных необходимо оперировать пикселями и вычислять положение каждого пикселя, удовлетворяющего условию круга. Круг в таком случае представлен как массив пикселей. В случае же применения языка программирования, работающего с векторными данными, достаточно указать функцию круга и параметры для неё.
Оперируя непрерывными данными мы лишаемся понятия события.
Там, где нам необходимы события и порядок выполнения действий над кусочками данных удобнее всего применять дискретно-событийный подход к обработке. Это задачи реакции на события от пользователя, интерпретация и вычленение событий из входящего потока данных, последовательное преобразование кусочков информации.
Там же, где нам нужна картина “в целом” наиболее удобен подход аналитических языков: это получение срезов данных, описание структур данных, решение математических функций, описание графики, непрерывные преобразования одного входящего потока в другой.
Автор: ymik