Автоматное программирование: различия между версиями
[непроверенная версия] | [непроверенная версия] |
Нет описания правки |
Leokand (обсуждение | вклад) м внутренние ссылки |
||
(не показано 40 промежуточных версий 27 участников) | |||
Строка 1: | Строка 1: | ||
{{Парадигмы программирования}} |
{{Парадигмы программирования}} |
||
'''Автома́тное программи́рование''' — |
'''Автома́тное программи́рование''' — [[парадигма программирования]], при использовании которой программа или её фрагмент осмысливается как модель какого-либо формального [[теория автоматов|автомата]]. Известна также и другая «парадигма автоматного программирования, состоящая в представлении сущностей со сложным поведением в виде автоматизированных объектов управления, каждый из которых представляет собой объект управления и автомат». При этом о программе, как в автоматическом управлении, предлагается думать, как о системе автоматизированных объектов управления. |
||
В зависимости от конкретной задачи в автоматном программировании могут использоваться как [[конечный автомат|конечные автоматы]], так и автоматы с более сложным строением. |
В зависимости от конкретной задачи в автоматном программировании могут использоваться как [[конечный автомат|конечные автоматы]], так и автоматы с более сложным строением. |
||
Строка 11: | Строка 11: | ||
Название '''автоматное программирование''' оправдывается ещё и тем, что стиль мышления (восприятия процесса исполнения) при программировании в этой технике практически точно воспроизводит стиль мышления при составлении формальных автоматов (таких как [[машина Тьюринга]], [[автомат Маркова]] и др.) |
Название '''автоматное программирование''' оправдывается ещё и тем, что стиль мышления (восприятия процесса исполнения) при программировании в этой технике практически точно воспроизводит стиль мышления при составлении формальных автоматов (таких как [[машина Тьюринга]], [[автомат Маркова]] и др.) |
||
<!-- |
|||
явное задание множества возможных [[состояние|состояний]] моделируемого автомата и хранение текущего состояния в одной или нескольких [[переменная (программирование)|переменных]]. Сам фрагмент программы, написанный в стиле автоматного программирования, имеет, как правило, точку входа, точку выхода и разделяется на несколько секций, одна и только одна из которых выбирается для исполнения в зависимости от текущего состояния. Каждый вызов такого фрагмента кода соответствует одному шагу работы конечного автомата. Предполагается, что такой фрагмент кода будет вызываться многократно, выполняя каждый раз сравнительно небольшой объем действий и, возможно, меняя при этом состояние. Конкретизация понятия «сравнительно небольшой объем» зависит от решаемой задачи, но обычно в коде, моделирующем шаг автомата, избегают длинных циклов. Кроме того, такой код обычно не содержит рекурсивных вызовов самого себя: если такая потребность возникает, вводят дополнительное состояние, переводят в него автомат и возвращают управление в предположении, что код будет вызван снова (то есть произойдёт следующий шаг автомата), на котором и будет сделано всё, из-за чего возникла потребность в рекурсивном вызове. |
|||
--> |
|||
== Пример с использованием [[конечный автомат|конечного автомата]] == |
== Пример с использованием [[конечный автомат|конечного автомата]] == |
||
Пусть, к примеру, требуется написать на языке [[Си (язык программирования)|Си]] программу, читающую из потока стандартного ввода текст, состоящий из строк, и для каждой строки печатающую первое слово этой строки и перевод строки. Ясно, что для этого во время чтения каждой строки следует сначала пропустить пробелы, если таковые есть в начале строки; затем читать буквы, составляющие слово, и печатать их, пока слово не кончится (то есть либо не кончится строка, либо не будет встречен пробельный символ); наконец, когда первое слово успешно считано и напечатано, необходимо дочитать строку до конца, ничего при этом не печатая. Встретив (на любой фазе) символ перевода строки, следует напечатать перевод строки и продолжить с начала. При возникновении (опять таки, на любой фазе) ситуации «конец файла» следует прекратить работу. |
|||
Пусть, к примеру, требуется написать на языке [[Си (язык программирования)|Си]] программу, читающую из потока стандартного ввода текст, состоящий из строк, и для каждой строки печатающую первое слово этой строки и перевод строки. Ясно, что для этого во время чтения каждой строки следует сначала пропустить пробелы, если таковые есть в начале строки; затем читать буквы, составляющие слово, и печатать их, пока слово не кончится (то есть либо не кончится строка, либо не будет встречен пробельный символ); наконец, когда первое слово успешно считано и напечатано, необходимо дочитать строку до конца, ничего при этом не печатая. Встретив (на любой фазе) символ перевода строки, следует напечатать перевод строки и продолжить сначала. При возникновении (опять таки, на любой фазе) ситуации «конец файла» следует прекратить работу. |
|||
=== Императивная программа === |
=== Императивная программа === |
||
Программа, решающая эту задачу в традиционном императивном стиле, может выглядеть, например, так ([[Си (язык программирования)|язык С]]): |
Программа, решающая эту задачу в традиционном императивном стиле, может выглядеть, например, так ([[Си (язык программирования)|язык С]]): |
||
<source lang="c"> |
<source lang="c" line="1"> |
||
#include <stdio.h> |
#include <stdio.h> |
||
int main() |
int main() |
||
{ |
|||
int c; |
int c; |
||
do { |
do { |
||
do |
|||
c = getchar(); |
|||
while (c == ' '); |
|||
while (c != ' |
while (c != ' ' && c != '\n' && c != EOF) { |
||
putchar(c); |
|||
c = getchar(); |
|||
} |
|||
putchar('\n'); |
|||
while (c != '\n' && c != EOF) |
|||
c = getchar(); |
|||
} while (c != EOF); |
|||
return 0; |
return 0; |
||
} |
} |
||
</source> |
</source> |
||
=== Программа в автоматном стиле === |
=== Программа в автоматном стиле === |
||
Ту же задачу можно решить, применив мышление в терминах конечных автоматов. Заметим, что разбор строки разделяется на три фазы: пропуск лидирующих пробелов, печать слова и пропуск символов остатка строки. Назовём эти три фазы '''состояниями''' <code>before</code>, <code>inside</code> и <code>after</code>. Программа теперь может выглядеть, например, так: |
Ту же задачу можно решить, применив мышление в терминах конечных автоматов. Заметим, что разбор строки разделяется на три фазы: пропуск лидирующих пробелов, печать слова и пропуск символов остатка строки. Назовём эти три фазы '''состояниями''' <code>before</code>, <code>inside</code> и <code>after</code>. Программа теперь может выглядеть, например, так: |
||
<source lang="c"> |
<source lang="c" line="1"> |
||
#include <stdio.h> |
#include <stdio.h> |
||
int main() |
int main() |
||
{ |
|||
enum states { before, inside, after } state; |
enum states { before, inside, after } state; |
||
int c; |
int c; |
||
state = before; |
state = before; |
||
while ((c = getchar()) != EOF) { |
while ((c = getchar()) != EOF) { |
||
switch (state) { |
switch (state) { |
||
case before: |
|||
if (c == '\n') { |
|||
putchar('\n'); |
|||
else if (c != ' ') |
} else if (c != ' ') { |
||
putchar(c); |
|||
state = inside; |
|||
} |
|||
break; |
|||
case inside: |
|||
switch (c) { |
|||
case ' |
case ' ': |
||
state = after; |
|||
break; |
|||
case '\n': |
|||
putchar('\n'); |
|||
state = before; |
|||
break; |
|||
default: |
|||
putchar(c); |
|||
} |
|||
break; |
break; |
||
case after: |
|||
if (c == '\n') { |
|||
putchar('\n'); |
|||
state = before; |
|||
} |
|||
} |
} |
||
break; |
|||
case after: |
|||
if (c == '\n') putchar('\n'), state = before; |
|||
} |
} |
||
return 0; |
|||
return 0; |
|||
} |
} |
||
</source> |
</source> |
||
или так:<syntaxhighlight lang="c" line="1"> |
|||
Несмотря на то, что код явно стал более длинным, у него имеется одно несомненное достоинство: чтение (то есть вызов функции <code>getchar()</code>) теперь выполняется '''ровно в одном месте'''. Кроме того, необходимо заметить, что вместо четырёх циклов, использовавшихся в предыдущей версии, цикл теперь используется только один. Тело цикла (за исключением действий, выполняемых в заголовке) представляет собой '''шаг автомата''', сам же цикл задаёт '''цикл работы автомата'''. |
|||
#include <stdio.h> |
|||
static void (*state)(int); |
|||
static void before(int c); |
|||
static void inside(int c); |
|||
static void after(int c); |
|||
void before(int c) |
|||
{ |
|||
if (c == '\n') { |
|||
putchar('\n'); |
|||
} else if (c != ' ') { |
|||
putchar(c); |
|||
state = &inside; |
|||
} |
|||
} |
|||
void inside(int c) |
|||
{ |
|||
switch (c) { |
|||
case ' ': |
|||
state = &after; |
|||
break; |
|||
case '\n': |
|||
putchar('\n'); |
|||
state = &before; |
|||
break; |
|||
default: |
|||
putchar(c); |
|||
} |
|||
} |
|||
void after(int c) |
|||
{ |
|||
if (c == '\n') { |
|||
putchar('\n'); |
|||
state = &before; |
|||
} |
|||
} |
|||
int main() |
|||
{ |
|||
int c; |
|||
state = &before; |
|||
while ((c = getchar()) != EOF) { |
|||
(*state)(c); |
|||
} |
|||
return 0; |
|||
} |
|||
</syntaxhighlight>Несмотря на то, что код явно стал более длинным, у него имеется одно несомненное достоинство: чтение (то есть вызов функции <code>getchar()</code>) теперь выполняется '''ровно в одном месте'''. Кроме того, необходимо заметить, что вместо четырёх циклов, использовавшихся в предыдущей версии, цикл теперь используется только один. Тело цикла (за исключением действий, выполняемых в заголовке) представляет собой '''шаг автомата''', сам же цикл задаёт '''цикл работы автомата'''. |
|||
[[Файл:Automata_that_prints_the_first_word_of_each_line.png|right|Диаграмма конечного автомата]] |
[[Файл:Automata_that_prints_the_first_word_of_each_line.png|right|Диаграмма конечного автомата]] |
||
Строка 91: | Строка 149: | ||
Строго соблюдать разделение кода на обработчики отдельных состояний, вообще говоря, не обязательно. Более того, в некоторых случаях само понятие состояния может складываться из значений нескольких переменных, так что учесть все возможные их комбинации окажется практически невозможно. В рассматриваемом примере можно сэкономить объём кода, если заметить, что действия, выполняемые по символу «конец строки», от состояния не зависят. Программа, эквивалентная предыдущей, но написанная с учётом такого замечания, будет выглядеть так: |
Строго соблюдать разделение кода на обработчики отдельных состояний, вообще говоря, не обязательно. Более того, в некоторых случаях само понятие состояния может складываться из значений нескольких переменных, так что учесть все возможные их комбинации окажется практически невозможно. В рассматриваемом примере можно сэкономить объём кода, если заметить, что действия, выполняемые по символу «конец строки», от состояния не зависят. Программа, эквивалентная предыдущей, но написанная с учётом такого замечания, будет выглядеть так: |
||
<source lang="c"> |
<source lang="c" line="1"> |
||
#include <stdio.h> |
#include <stdio.h> |
||
int main() |
int main() |
||
{ |
|||
enum states { before, inside, after } state; |
enum states { before, inside, after } state; |
||
int c; |
int c; |
||
state = before; |
state = before; |
||
while ((c = getchar()) != EOF) { |
while ((c = getchar()) != EOF) { |
||
if (c == '\n') |
if (c == '\n') { |
||
putchar('\n'); |
|||
state = before; |
|||
continue; |
|||
} |
|||
switch (state) { |
|||
case before: |
|||
if (c != ' ') { |
|||
putchar(c); |
|||
state = inside; |
|||
} |
|||
break; |
|||
case inside: |
|||
if (c == ' ') |
|||
state = after; |
|||
else |
|||
putchar(c); |
|||
break; |
|||
case after: |
|||
break; |
|||
} |
|||
} |
} |
||
} |
|||
return 0; |
return 0; |
||
} |
} |
||
</source> |
</source> |
||
=== Выделение шага автомата в отдельную функцию === |
=== Выделение шага автомата в отдельную функцию === |
||
Основополагающим в вышеприведённой программе является по-прежнему чёткое выделение кода, отвечающего за шаг автомата. Это обстоятельство можно подчеркнуть ещё сильнее, если выделить шаг автомата в отдельную функцию. |
Основополагающим в вышеприведённой программе является по-прежнему чёткое выделение кода, отвечающего за шаг автомата. Это обстоятельство можно подчеркнуть ещё сильнее, если выделить шаг автомата в отдельную функцию. |
||
<source lang="c"> |
<source lang="c" line="1"> |
||
#include <stdio.h> |
#include <stdio.h> |
||
enum |
enum state_t { before, inside, after }; |
||
void step(enum |
static void step(enum state_t *state, int *c) |
||
{ |
|||
if (*state == before) { |
|||
if (c == '\n') putchar('\n'), state = before; |
|||
if (*c == '\n') |
|||
switch (state) { |
|||
putchar('\n'); |
|||
case before: |
|||
else if (*c != ' ') |
|||
*state = inside; |
|||
break; |
|||
} |
|||
case inside: |
|||
if (*state == inside) { |
|||
if (*c == ' ') { |
|||
else putchar(c); |
|||
*state = after; |
|||
} else if (*c == '\n') { |
|||
break; |
|||
putchar('\n'); |
|||
} |
|||
*state = before; |
|||
} else { |
|||
putchar(*c); |
|||
} |
|||
} |
|||
if (*state == after) { |
|||
if (*c == '\n') { |
|||
putchar('\n'); |
|||
*state = before; |
|||
} |
|||
} |
|||
} |
} |
||
int main() |
int main() |
||
{ |
|||
int c; enum |
int c; |
||
enum state_t state = before; |
|||
while ((c = getchar()) != EOF) step(state, c); |
while ((c = getchar()) != EOF) |
||
step(&state, &c); |
|||
return 0; |
return 0; |
||
} |
} |
||
</source> |
</source> |
||
Строка 154: | Строка 237: | ||
=== Программа с явно заданной таблицей переходов === |
=== Программа с явно заданной таблицей переходов === |
||
Конечный автомат, как известно, может быть задан и таблицей переходов. Вообще говоря, код программы, моделирующей конечный автомат, вполне может отражать и это свойство автомата. В следующей программе массив <code>the_table</code> задаёт таблицу переходов. Строки таблицы соответствуют трём состояниям автомата, столбцы — читаемым символам (первый столбец — пробел, второй столбец — перевод строки, третий столбец — все остальные символы). Каждая ячейка таблицы содержит номер нового состояния и признак необходимости печати символа (в приведённом коде используются битовые поля для экономии памяти). Конечно, в реальной задаче могла бы потребоваться гораздо более сложная структура таблицы, содержащая, например, указатели на функции для выполнения каких-либо действий, связанных с переходами, но в рассматриваемом примере это не нужно: |
Конечный автомат, как известно, может быть задан и таблицей переходов. Вообще говоря, код программы, моделирующей конечный автомат, вполне может отражать и это свойство автомата. В следующей программе массив <code>the_table</code> задаёт таблицу переходов. Строки таблицы соответствуют трём состояниям автомата, столбцы — читаемым символам (первый столбец — пробел, второй столбец — перевод строки, третий столбец — все остальные символы). Каждая ячейка таблицы содержит номер нового состояния и признак необходимости печати символа (в приведённом коде используются битовые поля для экономии памяти). Конечно, в реальной задаче могла бы потребоваться гораздо более сложная структура таблицы, содержащая, например, указатели на функции для выполнения каких-либо действий, связанных с переходами, но в рассматриваемом примере это не нужно: |
||
<source lang="c"> |
<source lang="c" line="1"> |
||
#include <stdio.h> |
#include <stdio.h> |
||
enum states { before = 0, inside = 1, after = 2 |
enum states { |
||
before = 0, |
|||
inside = 1, |
|||
after = 2 |
|||
struct branch { |
|||
enum states new_state:4; |
|||
int should_putchar:4; |
|||
}; |
}; |
||
typedef struct branch { |
|||
branch the_table[3][3] = { |
|||
enum states new_state; |
|||
int should_putchar; |
|||
} branch; |
|||
static const branch the_table[3][3] = { |
|||
/* ' ' '\n' others */ |
/* ' ' '\n' others */ |
||
/* before */ { {before, 0}, {before, 1}, {inside, 1} }, |
/* before */ { {before, 0}, {before, 1}, {inside, 1} }, |
||
Строка 173: | Строка 259: | ||
}; |
}; |
||
void step(enum states *state, int c) |
static void step(enum states *state, int c) |
||
{ |
|||
int idx2 = (c == ' ') ? 0 : (c == '\n') ? 1 : 2; |
int idx2 = (c == ' ') ? 0 : (c == '\n') ? 1 : 2; |
||
branch *b = & |
branch *b = &the_table[*state][idx2]; |
||
*state = b->new_state; |
*state = b->new_state; |
||
if (b->should_putchar) putchar(c); |
if (b->should_putchar) |
||
putchar(c); |
|||
} |
} |
||
int main() |
int main() |
||
{ |
|||
int c; enum states state = before; |
int c; |
||
enum states state = before; |
|||
while ((c = getchar()) != EOF) step(&state, c); |
while ((c = getchar()) != EOF) |
||
step(&state, c); |
|||
return 0; |
return 0; |
||
} |
} |
||
</source> |
</source> |
||
=== Использование объектно-ориентированных возможностей === |
=== Использование объектно-ориентированных возможностей === |
||
Если используемый язык программирования поддерживает [[объектно-ориентированное программирование|объектно-ориентированные возможности]], логично будет [[инкапсуляция (программирование)|инкапсулировать]] конечный автомат в объект, скрыв детали реализации. Например, аналогичная программа на языке [[C++]] может выглядеть так: |
Если используемый язык программирования поддерживает [[объектно-ориентированное программирование|объектно-ориентированные возможности]], логично будет [[инкапсуляция (программирование)|инкапсулировать]] конечный автомат в объект, скрыв детали реализации. Например, аналогичная программа на языке [[C++]] может выглядеть так: |
||
<source lang=cpp> |
<source lang="cpp" line="1"> |
||
#include <stdio.h> |
#include <stdio.h> |
||
class StateMachine { |
class StateMachine { |
||
enum states { before = 0, inside = 1, after = 2 } state; |
enum states { |
||
before = 0, |
|||
inside = 1, |
|||
after = 2 |
|||
} state; |
|||
typedef struct { |
|||
enum states new_state |
enum states new_state; |
||
unsigned should_putchar |
unsigned should_putchar; |
||
}; |
} branch; |
||
static |
static const branch the_table[3][3]; |
||
public: |
public: |
||
StateMachine() : state(before) {} |
StateMachine() : state(before) {} |
||
void FeedChar(int c) { |
void FeedChar(int c) { |
||
int idx2 = (c == ' ') ? 0 : (c == '\n') ? 1 : 2; |
int idx2 = (c == ' ') ? 0 : (c == '\n') ? 1 : 2; |
||
branch *b = &the_table[state][idx2]; |
|||
state = b->new_state; |
state = b->new_state; |
||
if(b->should_putchar) putchar(c); |
if(b->should_putchar) |
||
putchar(c); |
|||
} |
} |
||
}; |
}; |
||
const StateMachine::branch StateMachine::the_table[3][3] = { |
|||
/* ' ' '\n' others */ |
/* ' ' '\n' others */ |
||
/* before */ { {before, 0}, {before, 1}, {inside, 1} }, |
/* before */ { {before, 0}, {before, 1}, {inside, 1} }, |
||
Строка 227: | Строка 323: | ||
return 0; |
return 0; |
||
} |
} |
||
</source>Также каждое состояние конечного автомата можно описать как класс.<syntaxhighlight lang="c++" line="1"> |
|||
</source> |
|||
#include <stdio.h> |
|||
Отметим, что в этом примере мы использовали для ввода-вывода библиотеку языка Си, чтобы избежать появления «лишних» (отвлекающих внимание) изменений в сравнении с предыдущим примером. |
|||
class State { |
|||
== Сфера применения == |
|||
public: |
|||
virtual ~State() {} |
|||
virtual void Action(int ch, const State *&next_state) const = 0; |
|||
}; |
|||
template <class T> |
|||
class TState : protected State { |
|||
static State *p; |
|||
public: |
|||
static State *Get() { |
|||
if(!p) |
|||
p = new T; |
|||
return p; |
|||
} |
|||
protected: |
|||
TState() {} |
|||
}; |
|||
class Before : public TState<Before> { |
|||
virtual void Action(int ch, const State *&next_state) const; |
|||
}; |
|||
class Inside : public TState<Inside> { |
|||
virtual void Action(int ch, const State *&next_state) const; |
|||
}; |
|||
class After : public TState<After> { |
|||
virtual void Action(int ch, const State *&next_state) const; |
|||
}; |
|||
template <> State *TState<Before>::p = 0; |
|||
template <> State *TState<Inside>::p = 0; |
|||
template <> State *TState<After>::p = 0; |
|||
void Before::Action(int ch, const State *&next_state) const |
|||
{ |
|||
if(ch != ' ' && ch != '\n') { |
|||
putchar(ch); |
|||
next_state = Inside::Get(); |
|||
} |
|||
} |
|||
void Inside::Action(int ch, const State *&next_state) const |
|||
{ |
|||
if(ch != ' ' && ch != '\n') { |
|||
putchar(ch); |
|||
} else { |
|||
putchar('\n'); |
|||
next_state = (ch == '\n' ? Before::Get() : After::Get()); |
|||
} |
|||
} |
|||
void After::Action(int ch, const State *&next_state) const |
|||
{ |
|||
if(ch == '\n') |
|||
next_state = Before::Get(); |
|||
} |
|||
class FiniteStateMashine { |
|||
const State *state; |
|||
public: |
|||
FiniteStateMashine() : state(Before::Get()) {} |
|||
void Step(int ch) |
|||
{ state->Action(ch, state); } |
|||
}; |
|||
int main() |
|||
{ |
|||
FiniteStateMashine fsm; |
|||
int ch; |
|||
while((ch = getchar()) != EOF) |
|||
fsm.Step(ch); |
|||
return 0; |
|||
} |
|||
</syntaxhighlight>Отметим, что в этом примере мы использовали для ввода-вывода библиотеку языка Си, чтобы избежать появления «лишних» (отвлекающих внимание) изменений в сравнении с предыдущим примером. |
|||
== Сфера применения == |
|||
Автоматное программирование широко применяется при построении [[лексический анализ|лексических анализаторов]] (классические конечные автоматы) и [[синтаксический анализ|синтаксических анализаторов]] ([[автомат с магазинной памятью|автоматы с магазинной памятью]])<ref name=gram> |
Автоматное программирование широко применяется при построении [[лексический анализ|лексических анализаторов]] (классические конечные автоматы) и [[синтаксический анализ|синтаксических анализаторов]] ([[автомат с магазинной памятью|автоматы с магазинной памятью]])<ref name=gram> |
||
{{книга |
{{книга |
||
Строка 251: | Строка 418: | ||
Мышление в терминах автоматов (шагов и состояний) находит применение и при описании семантики некоторых языков программирования. Так, исполнение программы на языке [[Рефал]] представляет собой последовательность изменений поля зрения Рефал-машины или, иначе говоря, последовательность шагов Рефал-автомата, состоянием которого является ''содержимое поля зрения'' (произвольное Рефал-выражение, не содержащее переменных). |
Мышление в терминах автоматов (шагов и состояний) находит применение и при описании семантики некоторых языков программирования. Так, исполнение программы на языке [[Рефал]] представляет собой последовательность изменений поля зрения Рефал-машины или, иначе говоря, последовательность шагов Рефал-автомата, состоянием которого является ''содержимое поля зрения'' (произвольное Рефал-выражение, не содержащее переменных). |
||
Механизм [[ |
Механизм [[Продолжение (информатика)|продолжений]] языка [[Scheme]] для своей реализации также требует мышления в терминах состояний и шагов, несмотря на то что сам язык Scheme никоим образом не является автоматным. Тем не менее, чтобы обеспечить возможность «замораживания» ''продолжения'', приходится при реализации вычислительной модели языка Scheme объединять все компоненты среды исполнения, включая ''список действий, которые осталось выполнить для окончания вычислений'', в единое целое, которое также обычно называется ''продолжением''. Такое ''продолжение'' оказывается состоянием автомата, а процесс выполнения программы состоит из шагов, каждый из которых выводит следующее значение ''продолжения'' из предыдущего. |
||
Александр Оллонгрен в своей книге<ref> |
Александр Оллонгрен в своей книге<ref> |
||
Строка 265: | Строка 432: | ||
</ref> описывает так называемый ''Венский метод'' описания семантики языков программирования, основанный целиком на формальных автоматах. |
</ref> описывает так называемый ''Венский метод'' описания семантики языков программирования, основанный целиком на формальных автоматах. |
||
В качестве одного из примеров применения автоматной парадигмы можно назвать систему STAT [http://www.cs.ucsb.edu/~seclab/projects/stat/index.html]; эта система, в частности, включает встроенный язык STATL, имеющий чисто автоматную семантику. |
В качестве одного из примеров применения автоматной парадигмы можно назвать систему STAT [https://web.archive.org/web/20080202163122/http://www.cs.ucsb.edu/~seclab/projects/stat/index.html]; эта система, в частности, включает встроенный язык STATL, имеющий чисто автоматную семантику. |
||
Существуют также предложения по использованию автоматного программирования в качестве универсального подхода к созданию компьютерных программ вне зависимости от предметной области. Так, авторы статьи<ref> |
Существуют также предложения по использованию автоматного программирования в качестве универсального подхода к созданию компьютерных программ вне зависимости от предметной области. Так, авторы статьи<ref>{{статья |
||
|автор = Туккель Н.И., Шалыто А.А. |
|||
{{статья |
|||
|заглавие = Программирование с явным выделением состояний |
|||
|автор = Туккель Н.И., Шалыто А.А. |
|||
|ссылка = http://is.ifmo.ru/works/mirk/ |
|||
|заглавие = Программирование с явным выделением состояний |
|||
|издание = Мир ПК |
|||
|ссылка = http://is.ifmo.ru/works/mirk/ |
|||
|год = 2001 |
|||
|издание = Мир ПК |
|||
| |
|номер = 9 |
||
| |
|страницы = 132—138 |
||
|archivedate = 2007-09-29 |
|||
|страницы = 132—138 |
|||
|archiveurl = https://web.archive.org/web/20070929130517/http://is.ifmo.ru/works/mirk/ |
|||
}} |
|||
</ref> утверждают, что автоматное программирование способно сыграть роль легендарной [[Серебряной пули нет|серебряной пули]]. |
}}</ref> утверждают, что автоматное программирование способно сыграть роль легендарной [[Серебряной пули нет|серебряной пули]]. |
||
== История == |
== История == |
||
Наиболее ранние случаи применения парадигмы автоматного программирования относятся, по-видимому, к предметным областям, в которых наработана алгоритмическая теория, основанная на [[теория автоматов|теории автоматов]], и прежде всего — к анализу текстов на формальных языках.<ref name=gram /> В качестве одной из наиболее ранних работ на эту тему можно назвать статью.<ref> |
Наиболее ранние случаи применения парадигмы автоматного программирования относятся, по-видимому, к предметным областям, в которых наработана алгоритмическая теория, основанная на [[теория автоматов|теории автоматов]], и прежде всего — к анализу текстов на формальных языках.<ref name=gram /> В качестве одной из наиболее ранних работ на эту тему можно назвать статью.<ref> |
||
{{статья |
|||
{{cite journal |
|||
|заглавие=Automatic generation of efficient lexical processors using finite state techniques |
|||
| last = Johnson |
|||
|издание=[[Communications of the ACM|Comm. ACM]] |
|||
| first = W. L. |
|||
|том=11 |
|||
| coauthors = Porter, J. H.; Ackley, S. I.; Ross, D. T. |
|||
|номер=12 |
|||
| year = 1968 |
|||
|страницы=805—813 |
|||
| title = Automatic generation of efficient lexical processors using finite state techniques |
|||
|язык=English |
|||
| journal = Comm. ACM |
|||
|автор=Johnson, W. L.; Porter, J. H.; Ackley, S. I.; Ross, D. T. |
|||
| volume = 11 |
|||
|год=1968}} |
|||
| issue = 12 |
|||
| pages = 805—813 |
|||
| language = English |
|||
}} |
|||
</ref> |
</ref> |
||
Одним из первых упоминаний использования техники автоматного программирования независимо от теоретических наработок, основанных на конечных автоматах, является статья [[Наур, Питер|Питера Наура]].<ref> |
Одним из первых упоминаний использования техники автоматного программирования независимо от теоретических наработок, основанных на конечных автоматах, является статья [[Наур, Питер|Питера Наура]].<ref> |
||
{{статья |
|||
{{cite journal |
|||
|заглавие=The design of the GIER ALGOL compiler Part II |
|||
| last = Naur |
|||
|издание={{Нп3|BIT Numerical Mathematics}} |
|||
| first = Peter |
|||
|том=3 |
|||
| year = 1963 |
|||
|doi=10.1007/BF01939983 |
|||
| month = September |
|||
|issn=0006-3835 |
|||
| title = The design of the GIER ALGOL compiler Part II |
|||
|страницы=145—166 |
|||
| journal = BIT Numerical Mathematics |
|||
|язык=English |
|||
| volume = 3 |
|||
|link=http://www.springerlink.com/content/l0304m60482u357p/ |
|||
| number = 3 |
|||
|автор=Naur, Peter |
|||
| doi = 10.1007/BF01939983 |
|||
|месяц=9 |
|||
| issn = 0006-3835 |
|||
|год=1963}} |
|||
| pages = 145-166 |
|||
| language = English |
|||
| link = http://www.springerlink.com/content/l0304m60482u357p/ |
|||
}} |
|||
</ref> В этой статье автор называет применённый подход «подходом машины Тьюринга» (''Turing machine approach''), но реально никакая [[машина Тьюринга]] в статье не строится; приведённый в статье подход удовлетворяет вышеприведённому определению '''автоматного программирования'''. |
</ref> В этой статье автор называет применённый подход «подходом машины Тьюринга» (''Turing machine approach''), но реально никакая [[машина Тьюринга]] в статье не строится; приведённый в статье подход удовлетворяет вышеприведённому определению '''автоматного программирования'''. |
||
== Сравнение с императивным и процедурным программированием == |
== Сравнение с императивным и процедурным программированием == |
||
Понятие '''состояния программы''' не является эксклюзивной особенностью автоматного программирования. Вообще говоря, '''состояние''' возникает при выполнении любой компьютерной программы и представляет собой совокупность всей информации, которая во время исполнения может изменяться. Так, '''состояние''' программы, выполненной в традиционном [[императивное программирование|императивном стиле]] состоит из |
Понятие '''состояния программы''' не является эксклюзивной особенностью автоматного программирования. Вообще говоря, '''состояние''' возникает при выполнении любой компьютерной программы и представляет собой совокупность всей информации, которая во время исполнения может изменяться. Так, '''состояние''' программы, выполненной в традиционном [[императивное программирование|императивном стиле]] состоит из |
||
# |
# совокупности значений всех глобальных переменных и содержимого динамической памяти |
||
# содержимого регистров общего назначения |
# содержимого регистров общего назначения |
||
# содержимого стека (включая адреса возвратов и значения локальных переменных) |
# содержимого стека (включая адреса возвратов и значения локальных переменных) |
||
Строка 327: | Строка 486: | ||
== Связь с объектно-ориентированным программированием == |
== Связь с объектно-ориентированным программированием == |
||
В теории [[объектно-ориентированное программирование|объектно-ориентированного программирования]] считается, что '''объект''' имеет '''внутреннее состояние''' и способен получать '''сообщения''', отвечать на них, отправлять сообщения другим объектам и в процессе обработки сообщений изменять своё внутреннее состояние. Более приближенное к практике понятие ''вызова метода объекта'' считается синонимом понятия ''отправки сообщения объекту''. |
В теории [[объектно-ориентированное программирование|объектно-ориентированного программирования]] считается, что '''объект''' имеет '''внутреннее состояние''' и способен получать '''сообщения''', отвечать на них, отправлять сообщения другим объектам и в процессе обработки сообщений изменять своё внутреннее состояние. Более приближенное к практике понятие ''вызова метода объекта'' считается синонимом понятия ''отправки сообщения объекту''. |
||
Строка 338: | Строка 496: | ||
В SFC программа описывается в виде схематической последовательности шагов, объединенных переходами. |
В SFC программа описывается в виде схематической последовательности шагов, объединенных переходами. |
||
* [[ДРАКОН (язык программирования)|Дракон-схемы]] — графический язык программирования, используется для программирования в ракетно-космической технике («[[Буран (космический корабль)|Буран]]», «[[Морской старт]]», «[[РТ-2ПМ|Тополь]]»). Существует бесплатный Дракон-редактор. |
* [[ДРАКОН (язык программирования)|Дракон-схемы]] — графический язык программирования, используется для программирования в ракетно-космической технике («[[Буран (космический корабль)|Буран]]», «[[Морской старт]]», «[[РТ-2ПМ|Тополь]]»). Существует бесплатный Дракон-редактор. |
||
* [http://reflex-language.narod.ru/ Язык Рефлекс] — Си-подобный язык программирования, ориентированный на описание сложных алгоритмов управления в задачах промышленной автоматизации. |
|||
== См. также == |
|||
== Примечания== |
|||
* [[Событийно-ориентированное программирование]] |
|||
== Примечания == |
|||
{{примечания}} |
{{примечания}} |
||
== Литература == |
== Литература == |
||
* {{книга |
* {{книга |
||
|автор = Поликарпова Н.И., Шалыто А.А. |
|автор = Поликарпова Н.И., Шалыто А.А. |
||
Строка 354: | Строка 513: | ||
|isbn = 978-5-388-00692-9 |
|isbn = 978-5-388-00692-9 |
||
}} |
}} |
||
* {{книга |
* {{книга |
||
|автор = Шалыто А.А. |
|автор = Шалыто А.А. |
||
Строка 364: | Строка 522: | ||
|isbn = |
|isbn = |
||
}} |
}} |
||
* [https://www.amazon.com/Practical-UML-Statecharts-Second-Event-Driven/dp/0750687061/ref=ntt_at_ep_dpi_1 Practical UML Statecharts in C/C++] Книга о реализации UML2 State Machine на С/C++. Нацелена, главным образом, на программирование встроенных систем реального времени. |
|||
* [http://www.amazon.com/Practical-UML-Statecharts-Second-Event-Driven/dp/0750687061/ref=ntt_at_ep_dpi_1 Practical UML Statecharts in C/C++] Книга о реализации UML2 State Machine на С/C++. Нацелена, главным образом, на программирование встроенных систем реального времени. |
|||
* Научно-технический вестник СПбГУ ИТМО. Выпуск 53. Автоматное программирование. 2008. http://ntv.ifmo.ru/file/journal/61.pdf |
* Научно-технический вестник СПбГУ ИТМО. Выпуск 53. Автоматное программирование. 2008. http://ntv.ifmo.ru/file/journal/61.pdf |
||
* {{книга |
* {{книга |
||
|автор = Зюбин В. Е. |
|автор = Зюбин В. Е. |
||
Строка 376: | Строка 531: | ||
|год = 2006 |
|год = 2006 |
||
|страниц = 96 |
|страниц = 96 |
||
|isbn = 5-94356-425- |
|isbn = 5-94356-425-X |
||
}} |
}} |
||
* {{книга |
* {{книга |
||
|автор = Непейвода Н.Н. |
|автор = Непейвода Н.Н. |
||
Строка 389: | Строка 543: | ||
|isbn = 5-9556-0023-X |
|isbn = 5-9556-0023-X |
||
}} |
}} |
||
* {{статья |
|||
|заглавие=Statecharts: A Visual Formalism for Complex Systems |
|||
* {{cite journal |
|||
|издание=Sci. Comput. Programming |
|||
| last = Harel |
|||
|номер=8 |
|||
| first = David |
|||
|страницы=231—274 |
|||
| year = 1987 |
|||
|ссылка=http://www.wisdom.weizmann.ac.il/~dharel/SCANNED.PAPERS/Statecharts.pdf |
|||
| title = Statecharts: A Visual Formalism for Complex Systems |
|||
|язык=en |
|||
| journal = Sci. Comput. Programming |
|||
|тип=journal |
|||
| issue = 8 |
|||
|автор=Harel, David |
|||
| pages = 231—274 |
|||
|год=1987}} |
|||
| url = http://www.wisdom.weizmann.ac.il/~dharel/SCANNED.PAPERS/Statecharts.pdf |
|||
* {{статья |
|||
}} |
|||
|заглавие=Using Statecharts for Hardware Description and Synthesis |
|||
|издание=IEEE Trans. Computer Aided Design of Integrated Circuits and Systems |
|||
* {{cite journal |
|||
|номер=8 |
|||
| last = Harel |
|||
|страницы=798—807 |
|||
| first = David |
|||
|язык=en |
|||
| coauthors = Drusinsky, D. |
|||
|тип=journal |
|||
| year = 1989 |
|||
|автор=Harel, David; Drusinsky, D. |
|||
| title = Using Statecharts for Hardware Description and Synthesis |
|||
|год=1989}} |
|||
| journal = IEEE Trans. Computer Aided Design of Integrated Circuits and Systems |
|||
| issue = 8 |
|||
| pages = 798—807 |
|||
}} |
|||
== Ссылки == |
== Ссылки == |
||
* [http://project.ifmo.ru/ «Автоматное программирование. Проекты» (архив проектов с открытой документацией)] |
* [https://web.archive.org/web/20091227145751/http://project.ifmo.ru/ «Автоматное программирование. Проекты» (архив проектов с открытой документацией)] |
||
* [http://www.softcraft.ru/design/ap |
* [http://www.softcraft.ru/design/ap Б. П. Кузнецов. «Психология автоматного программирования»] |
||
* [http://www.softcraft.ru/auto/ |
* [http://www.softcraft.ru/auto/other/itst Б. П. Кузнецов. «Настраиваемые автоматные программы»] |
||
* [http://erlang.org/doc/design_principles/fsm.html Erlang: Gen_Fsm Behaviour] Поддержка конечных автоматов в языке [[Erlang]] |
|||
* [http://legacy.imatix.com/html/libero/index.htm Libero home page] Libero — инструмент генерации кода, основанный на автоматном программировании (существует с 1982 года) |
|||
* [http://www.gnu.org/software/autogen/autofsm.html GNU AutoFSM] автоматический генератор кода на основе заданной машины состояний |
|||
* [http://www.complang.org/ragel/ Ragel] компилятор конечных автоматов со специализированного языка ragel в С, активно используется многими открытыми проектами |
|||
* [http://en.wikipedia.org/wiki/Automata-based_programming_%28Shalyto%27s_approach%29] Automata-based programming (Shalyto's approach) |
|||
* https://code.google.com/p/visio2python/ |
|||
* ''Митькин С.Б.'' [http://forum.oberoncore.ru/viewtopic.php?f=78&t=4284&hilit=%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82 Язык ДРАКОН и конечные автоматы] [[ДРАКОН]] |
|||
== См. также == |
|||
* [[Событийно-ориентированное программирование]] |
|||
[[Категория:Парадигмы программирования]] |
[[Категория:Парадигмы программирования]] |
Текущая версия от 09:17, 6 августа 2023
Автома́тное программи́рование — парадигма программирования, при использовании которой программа или её фрагмент осмысливается как модель какого-либо формального автомата. Известна также и другая «парадигма автоматного программирования, состоящая в представлении сущностей со сложным поведением в виде автоматизированных объектов управления, каждый из которых представляет собой объект управления и автомат». При этом о программе, как в автоматическом управлении, предлагается думать, как о системе автоматизированных объектов управления.
В зависимости от конкретной задачи в автоматном программировании могут использоваться как конечные автоматы, так и автоматы с более сложным строением.
Определяющими для автоматного программирования являются следующие особенности:
- временной период выполнения программы разбивается на шаги автомата, каждый из которых представляет собой выполнение определённой (одной и той же для каждого шага) секции кода с единственной точкой входа; такая секция может быть оформлена, например, в виде отдельной функции и может быть разделена на подсекции, соответствующие отдельным состояниям или категориям состояний
- передача информации между шагами автомата осуществляется только через явно обозначенное множество переменных, называемых состоянием автомата; между шагами автомата программа (или её часть, оформленная в автоматном стиле) не может содержать неявных элементов состояния, таких как значения локальных переменных в стеке, адреса возврата из функций, значение текущего счётчика команд и т. п.; иначе говоря, состояние программы на любые два момента входа в шаг автомата могут различаться между собой только значениями переменных, составляющих состояние автомата (причём такие переменные должны быть явно обозначены в качестве таковых).
Полностью выполнение кода в автоматном стиле представляет собой цикл (возможно, неявный) шагов автомата.
Название автоматное программирование оправдывается ещё и тем, что стиль мышления (восприятия процесса исполнения) при программировании в этой технике практически точно воспроизводит стиль мышления при составлении формальных автоматов (таких как машина Тьюринга, автомат Маркова и др.)
Пример с использованием конечного автомата
[править | править код]Пусть, к примеру, требуется написать на языке Си программу, читающую из потока стандартного ввода текст, состоящий из строк, и для каждой строки печатающую первое слово этой строки и перевод строки. Ясно, что для этого во время чтения каждой строки следует сначала пропустить пробелы, если таковые есть в начале строки; затем читать буквы, составляющие слово, и печатать их, пока слово не кончится (то есть либо не кончится строка, либо не будет встречен пробельный символ); наконец, когда первое слово успешно считано и напечатано, необходимо дочитать строку до конца, ничего при этом не печатая. Встретив (на любой фазе) символ перевода строки, следует напечатать перевод строки и продолжить с начала. При возникновении (опять таки, на любой фазе) ситуации «конец файла» следует прекратить работу.
Императивная программа
[править | править код]Программа, решающая эту задачу в традиционном императивном стиле, может выглядеть, например, так (язык С):
#include <stdio.h>
int main()
{
int c;
do {
do
c = getchar();
while (c == ' ');
while (c != ' ' && c != '\n' && c != EOF) {
putchar(c);
c = getchar();
}
putchar('\n');
while (c != '\n' && c != EOF)
c = getchar();
} while (c != EOF);
return 0;
}
Программа в автоматном стиле
[править | править код]Ту же задачу можно решить, применив мышление в терминах конечных автоматов. Заметим, что разбор строки разделяется на три фазы: пропуск лидирующих пробелов, печать слова и пропуск символов остатка строки. Назовём эти три фазы состояниями before
, inside
и after
. Программа теперь может выглядеть, например, так:
#include <stdio.h>
int main()
{
enum states { before, inside, after } state;
int c;
state = before;
while ((c = getchar()) != EOF) {
switch (state) {
case before:
if (c == '\n') {
putchar('\n');
} else if (c != ' ') {
putchar(c);
state = inside;
}
break;
case inside:
switch (c) {
case ' ':
state = after;
break;
case '\n':
putchar('\n');
state = before;
break;
default:
putchar(c);
}
break;
case after:
if (c == '\n') {
putchar('\n');
state = before;
}
}
}
return 0;
}
или так:
#include <stdio.h>
static void (*state)(int);
static void before(int c);
static void inside(int c);
static void after(int c);
void before(int c)
{
if (c == '\n') {
putchar('\n');
} else if (c != ' ') {
putchar(c);
state = &inside;
}
}
void inside(int c)
{
switch (c) {
case ' ':
state = &after;
break;
case '\n':
putchar('\n');
state = &before;
break;
default:
putchar(c);
}
}
void after(int c)
{
if (c == '\n') {
putchar('\n');
state = &before;
}
}
int main()
{
int c;
state = &before;
while ((c = getchar()) != EOF) {
(*state)(c);
}
return 0;
}
Несмотря на то, что код явно стал более длинным, у него имеется одно несомненное достоинство: чтение (то есть вызов функции getchar()
) теперь выполняется ровно в одном месте. Кроме того, необходимо заметить, что вместо четырёх циклов, использовавшихся в предыдущей версии, цикл теперь используется только один. Тело цикла (за исключением действий, выполняемых в заголовке) представляет собой шаг автомата, сам же цикл задаёт цикл работы автомата.
Программа реализует (моделирует) работу конечного автомата, изображённого на рисунке. Буквой N на диаграмме обозначен символ конца строки, буквой S — символ пробела, буквой A — все остальные символы. За один шаг автомат делает ровно один переход в зависимости от текущего состояния и прочитанного символа. Некоторые переходы сопровождаются печатью прочитанного символа; такие переходы на диаграмме обозначены звёздочками.
Строго соблюдать разделение кода на обработчики отдельных состояний, вообще говоря, не обязательно. Более того, в некоторых случаях само понятие состояния может складываться из значений нескольких переменных, так что учесть все возможные их комбинации окажется практически невозможно. В рассматриваемом примере можно сэкономить объём кода, если заметить, что действия, выполняемые по символу «конец строки», от состояния не зависят. Программа, эквивалентная предыдущей, но написанная с учётом такого замечания, будет выглядеть так:
#include <stdio.h>
int main()
{
enum states { before, inside, after } state;
int c;
state = before;
while ((c = getchar()) != EOF) {
if (c == '\n') {
putchar('\n');
state = before;
continue;
}
switch (state) {
case before:
if (c != ' ') {
putchar(c);
state = inside;
}
break;
case inside:
if (c == ' ')
state = after;
else
putchar(c);
break;
case after:
break;
}
}
return 0;
}
Выделение шага автомата в отдельную функцию
[править | править код]Основополагающим в вышеприведённой программе является по-прежнему чёткое выделение кода, отвечающего за шаг автомата. Это обстоятельство можно подчеркнуть ещё сильнее, если выделить шаг автомата в отдельную функцию.
#include <stdio.h>
enum state_t { before, inside, after };
static void step(enum state_t *state, int *c)
{
if (*state == before) {
if (*c == '\n')
putchar('\n');
else if (*c != ' ')
*state = inside;
}
if (*state == inside) {
if (*c == ' ') {
*state = after;
} else if (*c == '\n') {
putchar('\n');
*state = before;
} else {
putchar(*c);
}
}
if (*state == after) {
if (*c == '\n') {
putchar('\n');
*state = before;
}
}
}
int main()
{
int c;
enum state_t state = before;
while ((c = getchar()) != EOF)
step(&state, &c);
return 0;
}
Этот пример наглядно демонстрирует основное свойство, благодаря которому код можно считать оформленным в стиле автоматного программирования:
- отдельные шаги автомата выполняются в неперекрывающиеся временные периоды
- единственным средством передачи информации между шагами является явно определённое состояние (в данном случае переменная
state
)
Программа с явно заданной таблицей переходов
[править | править код]Конечный автомат, как известно, может быть задан и таблицей переходов. Вообще говоря, код программы, моделирующей конечный автомат, вполне может отражать и это свойство автомата. В следующей программе массив the_table
задаёт таблицу переходов. Строки таблицы соответствуют трём состояниям автомата, столбцы — читаемым символам (первый столбец — пробел, второй столбец — перевод строки, третий столбец — все остальные символы). Каждая ячейка таблицы содержит номер нового состояния и признак необходимости печати символа (в приведённом коде используются битовые поля для экономии памяти). Конечно, в реальной задаче могла бы потребоваться гораздо более сложная структура таблицы, содержащая, например, указатели на функции для выполнения каких-либо действий, связанных с переходами, но в рассматриваемом примере это не нужно:
#include <stdio.h>
enum states {
before = 0,
inside = 1,
after = 2
};
typedef struct branch {
enum states new_state;
int should_putchar;
} branch;
static const branch the_table[3][3] = {
/* ' ' '\n' others */
/* before */ { {before, 0}, {before, 1}, {inside, 1} },
/* inside */ { {after, 0}, {before, 1}, {inside, 1} },
/* after */ { {after, 0}, {before, 1}, {after, 0} }
};
static void step(enum states *state, int c)
{
int idx2 = (c == ' ') ? 0 : (c == '\n') ? 1 : 2;
branch *b = &the_table[*state][idx2];
*state = b->new_state;
if (b->should_putchar)
putchar(c);
}
int main()
{
int c;
enum states state = before;
while ((c = getchar()) != EOF)
step(&state, c);
return 0;
}
Использование объектно-ориентированных возможностей
[править | править код]Если используемый язык программирования поддерживает объектно-ориентированные возможности, логично будет инкапсулировать конечный автомат в объект, скрыв детали реализации. Например, аналогичная программа на языке C++ может выглядеть так:
#include <stdio.h>
class StateMachine {
enum states {
before = 0,
inside = 1,
after = 2
} state;
typedef struct {
enum states new_state;
unsigned should_putchar;
} branch;
static const branch the_table[3][3];
public:
StateMachine() : state(before) {}
void FeedChar(int c) {
int idx2 = (c == ' ') ? 0 : (c == '\n') ? 1 : 2;
branch *b = &the_table[state][idx2];
state = b->new_state;
if(b->should_putchar)
putchar(c);
}
};
const StateMachine::branch StateMachine::the_table[3][3] = {
/* ' ' '\n' others */
/* before */ { {before, 0}, {before, 1}, {inside, 1} },
/* inside */ { {after, 0}, {before, 1}, {inside, 1} },
/* after */ { {after, 0}, {before, 1}, {after, 0} }
};
int main()
{
int c;
StateMachine machine;
while((c = getchar()) != EOF)
machine.FeedChar(c);
return 0;
}
Также каждое состояние конечного автомата можно описать как класс.
#include <stdio.h>
class State {
public:
virtual ~State() {}
virtual void Action(int ch, const State *&next_state) const = 0;
};
template <class T>
class TState : protected State {
static State *p;
public:
static State *Get() {
if(!p)
p = new T;
return p;
}
protected:
TState() {}
};
class Before : public TState<Before> {
virtual void Action(int ch, const State *&next_state) const;
};
class Inside : public TState<Inside> {
virtual void Action(int ch, const State *&next_state) const;
};
class After : public TState<After> {
virtual void Action(int ch, const State *&next_state) const;
};
template <> State *TState<Before>::p = 0;
template <> State *TState<Inside>::p = 0;
template <> State *TState<After>::p = 0;
void Before::Action(int ch, const State *&next_state) const
{
if(ch != ' ' && ch != '\n') {
putchar(ch);
next_state = Inside::Get();
}
}
void Inside::Action(int ch, const State *&next_state) const
{
if(ch != ' ' && ch != '\n') {
putchar(ch);
} else {
putchar('\n');
next_state = (ch == '\n' ? Before::Get() : After::Get());
}
}
void After::Action(int ch, const State *&next_state) const
{
if(ch == '\n')
next_state = Before::Get();
}
class FiniteStateMashine {
const State *state;
public:
FiniteStateMashine() : state(Before::Get()) {}
void Step(int ch)
{ state->Action(ch, state); }
};
int main()
{
FiniteStateMashine fsm;
int ch;
while((ch = getchar()) != EOF)
fsm.Step(ch);
return 0;
}
Отметим, что в этом примере мы использовали для ввода-вывода библиотеку языка Си, чтобы избежать появления «лишних» (отвлекающих внимание) изменений в сравнении с предыдущим примером.
Сфера применения
[править | править код]Автоматное программирование широко применяется при построении лексических анализаторов (классические конечные автоматы) и синтаксических анализаторов (автоматы с магазинной памятью)[1].
Кроме того, мышление в терминах конечных автоматов (то есть разбиение исполнения программы на шаги автомата и передача информации от шага к шагу через состояние) необходимо при построении событийно-ориентированных приложений. В этом случае программирование в стиле конечных автоматов оказывается единственной альтернативой порождению множества процессов или потоков управления (тредов).
Часто понятие состояний и машин состояний используется для спецификации программ. Так, при проектировании программного обеспечения с помощью UML для описания поведения объектов используются диаграммы состояний (state machine diagrams). Кроме того, явное выделение состояний используется в описании сетевых протоколов (см., например, RFC 793[2]).
Мышление в терминах автоматов (шагов и состояний) находит применение и при описании семантики некоторых языков программирования. Так, исполнение программы на языке Рефал представляет собой последовательность изменений поля зрения Рефал-машины или, иначе говоря, последовательность шагов Рефал-автомата, состоянием которого является содержимое поля зрения (произвольное Рефал-выражение, не содержащее переменных).
Механизм продолжений языка Scheme для своей реализации также требует мышления в терминах состояний и шагов, несмотря на то что сам язык Scheme никоим образом не является автоматным. Тем не менее, чтобы обеспечить возможность «замораживания» продолжения, приходится при реализации вычислительной модели языка Scheme объединять все компоненты среды исполнения, включая список действий, которые осталось выполнить для окончания вычислений, в единое целое, которое также обычно называется продолжением. Такое продолжение оказывается состоянием автомата, а процесс выполнения программы состоит из шагов, каждый из которых выводит следующее значение продолжения из предыдущего.
Александр Оллонгрен в своей книге[3] описывает так называемый Венский метод описания семантики языков программирования, основанный целиком на формальных автоматах.
В качестве одного из примеров применения автоматной парадигмы можно назвать систему STAT [1]; эта система, в частности, включает встроенный язык STATL, имеющий чисто автоматную семантику.
Существуют также предложения по использованию автоматного программирования в качестве универсального подхода к созданию компьютерных программ вне зависимости от предметной области. Так, авторы статьи[4] утверждают, что автоматное программирование способно сыграть роль легендарной серебряной пули.
История
[править | править код]Наиболее ранние случаи применения парадигмы автоматного программирования относятся, по-видимому, к предметным областям, в которых наработана алгоритмическая теория, основанная на теории автоматов, и прежде всего — к анализу текстов на формальных языках.[1] В качестве одной из наиболее ранних работ на эту тему можно назвать статью.[5]
Одним из первых упоминаний использования техники автоматного программирования независимо от теоретических наработок, основанных на конечных автоматах, является статья Питера Наура.[6] В этой статье автор называет применённый подход «подходом машины Тьюринга» (Turing machine approach), но реально никакая машина Тьюринга в статье не строится; приведённый в статье подход удовлетворяет вышеприведённому определению автоматного программирования.
Сравнение с императивным и процедурным программированием
[править | править код]Понятие состояния программы не является эксклюзивной особенностью автоматного программирования. Вообще говоря, состояние возникает при выполнении любой компьютерной программы и представляет собой совокупность всей информации, которая во время исполнения может изменяться. Так, состояние программы, выполненной в традиционном императивном стиле состоит из
- совокупности значений всех глобальных переменных и содержимого динамической памяти
- содержимого регистров общего назначения
- содержимого стека (включая адреса возвратов и значения локальных переменных)
- текущего значения счётчика команд (то есть текущей позиции в коде программы)
Составные части состояния можно разделить на явные (такие как значения переменных) и неявные (адреса возвратов и значение счётчика команд).
В контексте введённых определений программу, оформленную в виде модели конечного автомата, можно считать частным случаем императивной программы, таким, в котором роль неявной составляющей состояния сведена к минимуму. Если рассмотреть автоматную программу в моменты начала очередного шага автомата, то состояния программы в эти моменты будут различаться только явной составляющей. Это обстоятельство существенно упрощает анализ свойств программы.
Связь с объектно-ориентированным программированием
[править | править код]В теории объектно-ориентированного программирования считается, что объект имеет внутреннее состояние и способен получать сообщения, отвечать на них, отправлять сообщения другим объектам и в процессе обработки сообщений изменять своё внутреннее состояние. Более приближенное к практике понятие вызова метода объекта считается синонимом понятия отправки сообщения объекту.
Таким образом, с одной стороны, объекты в объектно-ориентированном программировании могут рассматриваться как конечные автоматы (или, если угодно, модели конечных автоматов), состояние которых представляет собой совокупность внутренних полей, в качестве же шага автомата могут рассматриваться один или несколько методов объекта при условии, что эти методы не вызывают ни сами себя, ни друг друга ни прямо, ни косвенно.
С другой стороны, очевидно, что понятие объекта представляет собой удачный инструмент реализации модели конечного автомата. При применении парадигмы автоматного программирования в объектно-ориентированных языках обычно модели автоматов представляются в виде классов, состояние автомата описывается внутренними (закрытыми) полями класса, а код шага автомата оформляется в виде метода класса, причём такой метод скорее всего оказывается единственным открытым методом (не считая конструкторов и деструкторов), изменяющим состояние автомата. Другие открытые методы могут служить для получения информации о состоянии автомата, но не меняют его. Все вспомогательные методы (например, методы-обработчики отдельных состояний или их категорий) в таких случаях обычно убирают в закрытую часть класса.
Специализированные языки программирования
[править | править код]- Язык последовательных функциональных схем SFC (Sequential Function Chart) — графический язык программирования широко используется для программирования промышленных логических контроллеров PLC.
В SFC программа описывается в виде схематической последовательности шагов, объединенных переходами.
- Дракон-схемы — графический язык программирования, используется для программирования в ракетно-космической технике («Буран», «Морской старт», «Тополь»). Существует бесплатный Дракон-редактор.
См. также
[править | править код]Примечания
[править | править код]- ↑ 1 2 А. Ахо, Дж. Ульман. Теория синтаксического анализа, перевода и компиляции = The theory of parsing, translation and compiling. — М.: МИР, 1978. — Т. 1. — 612 с.
- ↑ Postel, J., ed., Transmission Control Protocol, RFC 793
- ↑ А. Оллонгрен. Определение языков программирования интерпретирующими автоматами = Definition of programming languages by interpreting automata. — М.: МИР, 1977. — 288 с.
- ↑ Туккель Н.И., Шалыто А.А. Программирование с явным выделением состояний // Мир ПК. — 2001. — № 9. — С. 132—138. Архивировано 29 сентября 2007 года.
- ↑ Johnson, W. L.; Porter, J. H.; Ackley, S. I.; Ross, D. T. Automatic generation of efficient lexical processors using finite state techniques (англ.) // Comm. ACM. — 1968. — Т. 11, № 12. — С. 805—813.
- ↑ Naur, Peter. The design of the GIER ALGOL compiler Part II (англ.) // BIT Numerical Mathematics[англ.]. — 1963. — Сентябрь (т. 3). — С. 145—166. — ISSN 0006-3835. — doi:10.1007/BF01939983.
Литература
[править | править код]- Поликарпова Н.И., Шалыто А.А. Автоматное программирование[2]. — СПб.: Питер, 2009. — 176 с. — ISBN 978-5-388-00692-9.
- Шалыто А.А. Switch-технология. Алгоритмизация и программирование задач логического управления.. — СПб.: Наука, 1998. — 628 с.
- Practical UML Statecharts in C/C++ Книга о реализации UML2 State Machine на С/C++. Нацелена, главным образом, на программирование встроенных систем реального времени.
- Научно-технический вестник СПбГУ ИТМО. Выпуск 53. Автоматное программирование. 2008. http://ntv.ifmo.ru/file/journal/61.pdf
- Зюбин В. Е. Программирование информационно-управляющих систем на основе конечных автоматов: учебное пособие.. — Новосибирск: Изд-во: Новосиб. гос. ун-т, 2006. — 96 с. — ISBN 5-94356-425-X.
- Непейвода Н.Н. Стили и методы программирования. курс лекций. учебное пособие. — М.: Интернет-университет информационных технологий, 2005. — С. 145—212. — 316 с. — ISBN 5-9556-0023-X.
- Harel, David. Statecharts: A Visual Formalism for Complex Systems (англ.) // Sci. Comput. Programming : journal. — 1987. — No. 8. — P. 231—274.
- Harel, David; Drusinsky, D. Using Statecharts for Hardware Description and Synthesis (англ.) // IEEE Trans. Computer Aided Design of Integrated Circuits and Systems : journal. — 1989. — No. 8. — P. 798—807.