Конечный автомат (FSM) – инструмент программиста

от автора

Тема конечных автоматов (КА) актуальна. Почти как тема реализации светофоров. Но вот, если серьезно, только отношение к ней разное. Время от времени появляются статьи типа «Конечные автоматы (FSM) – это ловушка для программиста» [1]. И здесь очень не хочется, чтобы складывалось превратное представление о некой «псевдо-математической» автоматной абстракции. Нужно оберегать народ от подобных суждений, которые ни на чем не основываются.

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

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

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

От программы к автомату

Мы не будем напрягаться, чтобы создать оригинальное решение (тем более, что на нашу реализацию светофора можно посмотреть в [2)], а переведем существующее решение в автоматную форму. Сделаем это с упором на науку, т.е. используя давно известную процедуру преобразования любой блок-схемы алгоритма в конечный автомат. Занимает это буквально минуты. Ниже листинг 1 представляет исходный код программы, а листинг 2 ее автоматный аналог.

Листинг 1. Код исходной программы
tablRec =  Table.firstRec();while(true){setLampsState(tablRec.colors);timer  = setTimer(tablRec.period);waitFor(timer); tablRec = nextRec(Table);if(tablRec == 0) tablRec =  Table.firstRec();}
Листинг 2. Код автоматной программы
LArc TBL_TraffikRukhi7[] = {    LArc("s1","s2","--","y1"),      // начало. 1-я строка    LArc("s2","s3","--","y2y3"),    // цвет; таймер    LArc("s3","s4","--","y4"),      // след строка    LArc("s4","s2","x2","y1"),      // на 1-ю строку    LArc("s4","s2","^x2","--"),      // очередная строка таблицы    LArc()};int FTraffikRukhi7::x2() { return Table.nCurrentIndex == int(Table.Table.size()); }void FTraffikRukhi7::y1() { tablRec =  Table.firstRec(); }void FTraffikRukhi7::y2() { setLampsState(tablRec.colors); }void FTraffikRukhi7::y3() { setTimer(tablRec.period); }void FTraffikRukhi7::y4() { tablRec  = Table.nextRec(); }

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

Листинг 3. Реализация таблицы для светофора
class RLine{public:    enum {None, Red, Yellow, Green};public:    RLine() {};    RLine(int nC, int nT) { colors = nC; period = nT; }    int colors{0};    int period{0};    bool operator==(const RLine &var) const    {        if (colors!=var.colors) return false;        if (period!=var.period) return false;        else return true;    }};class RTable{public:    RTable() {};    void Add(RLine line) { Table.push_back(line); }    RLine firstRec() { nCurrentIndex = 0; return Table[0]; }    RLine nextRec() {        int nn = int(Table.size());        int nIndex = nCurrentIndex;        if (nIndex>=nn) {            nCurrentIndex= 0;            return RLine(0, 0);        }        nCurrentIndex++;        return Table[nIndex];    }    vector<RLine> Table;    int nCurrentIndex{0};};

Код светофора демонстрирует листинг 4, где он приведен без кода автомата, который рассмотрен ранее (см. листинге 2).

Листинг 4. Код светофора, исключая автомат
#include "lfsaappl.h"#include "RTable.h"extern LArc TBL_TraffikRukhi7[];class FTraffikRukhi7 :    public LFsaAppl{public:    bool FCreationOfLinksForVariables();    FTraffikRukhi7(string strNam, LArc *pTBL=TBL_TraffikRukhi7);    virtual ~FTraffikRukhi7(void);    CVar    *pVarRed;    CVar    *pVarYellow;    CVar    *pVarGreen;    CVar    *pVarStrInitColor;    RTable  Table;    RLine tablRec;    void setLampsState(int clr);    void setTimer(int tmr);    int timer;protected:    int x2();    void y1(); void y2(); void y3(); void y4();};#include "stdafx.h"#include "FTraffikRukhi7.h"FTraffikRukhi7::FTraffikRukhi7(string strNam, LArc *pTBL):    LFsaAppl(pTBL, strNam, nullptr){    Table.Add(RLine(RLine::Red, 20));    Table.Add(RLine(RLine::Yellow, 5));    Table.Add(RLine(RLine::Green, 17));    Table.Add(RLine(RLine::None, 1));    Table.Add(RLine(RLine::Green, 1));    Table.Add(RLine(RLine::None, 1));    Table.Add(RLine(RLine::Green, 1));    Table.Add(RLine(RLine::None, 1));    Table.Add(RLine(RLine::Green, 1));    Table.Add(RLine(RLine::None, 1));    Table.Add(RLine(RLine::Green, 1));}FTraffikRukhi7::~FTraffikRukhi7(void){}bool FTraffikRukhi7::FCreationOfLinksForVariables() {    pVarRed = CreateLocVar("bRed", CLocVar::vtBool, ""); pVarRed->bSerial = true;    pVarYellow = CreateLocVar("bYellow", CLocVar::vtBool, ""); pVarYellow->bSerial = true;    pVarGreen = CreateLocVar("bGreen", CLocVar::vtBool, ""); pVarGreen->bSerial = true;    pVarStrInitColor = CreateLocVar("nInitColor", CLocVar::vtString, "", true, "Red");    return true;}void FTraffikRukhi7::setLampsState(int clr) {    switch(clr) {    case RLine::Red:        pVarRed->SetDataSrc(nullptr, 1.0);        pVarYellow->SetDataSrc(nullptr, 0.0);        pVarGreen->SetDataSrc(nullptr, 0.0);        break;    case RLine::Yellow:        pVarRed->SetDataSrc(nullptr, 0.0);        pVarYellow->SetDataSrc(nullptr, 1.0);        pVarGreen->SetDataSrc(nullptr, 0.0);        break;    case RLine::Green:        pVarRed->SetDataSrc(nullptr, 0.0);        pVarYellow->SetDataSrc(nullptr, 0.0);        pVarGreen->SetDataSrc(nullptr, 1.0);        break;    default:        pVarRed->SetDataSrc(nullptr, 0.0);        pVarYellow->SetDataSrc(nullptr, 0.0);        pVarGreen->SetDataSrc(nullptr, 0.0);        break;    }}void FTraffikRukhi7::setTimer(int tmr) { FCreateDelay(tmr*1000); }

Мы не будем рассматривать подключение процесса светофора к автоматному ядру, т.к. это не сложно, стандартно и занимает буквально десяток строк. Да и не очень интересно с точки зрения теории КА и работы приложения. Сама форма диалога и работа светофора представлены на гиф 1.

Gif 1. Работа светофора

Gif 1. Работа светофора

Заключение

Конечные автоматы – ловушка для дилетанта, но инструмент профессионала. Инструмент удобный и эффективный. Безусловно, для этого нужно уверенно владеть моделью КА, а в идеале иметь представление о возможностях теории автоматов. Хотя бы ее начал. Только в этом случае технология автоматного программирования станет вашим повседневным инструментом. Инструментом полноценным и по-настоящему эффективным.

Думаю, сказанных слов в пользу КА в этот раз вполне достаточно. Но если возникнет желание вникнуть в тему детальнее, то начните хотя бы с работы [3].

Литература

1.       Конечный автомат (FSM) – ловушка для программиста.   https://habr.com/ru/articles/1044244/

2.       Но почему, почему, почему был светофор зеленый? https://habr.com/ru/articles/1044514/

3.       Автоматная модель управления программ. https://habr.com/ru/articles/484588/

ссылка на оригинал статьи https://habr.com/ru/articles/1045614/