LabVIEWと同様なプログラミングによってWeb Applicationを作成することができるG Web Development Software (以下GWDS)を触ったことがない方に向けて、基本的な事柄を解説していこうという試みです。なお、大部分の事柄は、GWDSの前身であるLabVIEW NXG Web Moduleと共通します。
シリーズ10回目としてこれまでの知識を総動員したステートマシンの実装方法を扱っています。
この記事は、以下のような方に向けて書いています。
- プログラムを書くときに何を考えたらいいのか分からない
- ステートマシンって何?
- ステートマシンの例が知りたい
もし上記のことに興味があるよ、という方には参考にして頂けるかもしれません。
なお、前回の記事はこちらです。
デザインテンプレートを知る
前回の記事では、プロパティノードやタイプ定義を扱いました。これらはGWDSのアルゴリズムそのものに直接関係あるというよりは、作れるプログラムの幅を広げるための知識、みたいな位置づけです。
実は、(少なくとも2022年現在の現行バージョンでの)GWDSでプログラムを組むために必要なアルゴリズムの構築に関わる要素は半分以上紹介し終えています。データの処理される順番といった超基本的なことから、WhileループやForループを使った処理の繰り返しの方法、あるいは条件分岐のためのケースストラクチャ、効率のいいプログラムを書くためのイベントストラクチャ、そしてプロパティ変更等です。
でも、いざプログラムを書けと言われて組むことはとても難しいと思います。組むために必要な知識はある程度揃っていても、それと実際にプログラムを組むのとはまた別の難しさがあるんじゃないでしょうか。
ただ単にいくつかの関数を組み合わせて完成するだけのプログラムならともかく、いくつかの機能を実装した、そこそこ大きいプログラムはそもそもどうやって組めばいいのか、よくわからないと思います。
プログラミングをする際には、どういったこと(繰り返し、条件分岐など)をどういう風に(ループやケースストラクチャ)行うかといった知識だけでなく、それらをどう組み合わせていくかの考え方も必要になってきます。その考え方も知ることで初めてプログラムが書けるようになると思います。
でもプログラムの書き方の考え方って何を見ればいいの?実例を見るしかない??(このブログの記事ではGWDSではないですが、LabVIEWのプログラム例はそこそこ載せているつもりですが・・・)
そんなとき、デザインテンプレートが役に立ちます。プログラムの設計(デザイン)なんてそんなに無限にパターンがあるわけではなく、大体「よくこういう形になるな」という典型的な形(テンプレート)があり、これがデザインテンプレートと呼ばれています。
そしてそういったデザインテンプレートは組めば組むほど色々応用ができるようになります。そのためテンプレートと侮るなかれ、今回の記事で紹介するステートマシンを理解して初心者を卒業しちゃってください。
ステートマシンとは
今までさんざん書いてきた「ステートマシン」なるデザインテンプレートは、「ステートを遷移してプログラムが進んでいく」タイプのプログラムを作るための書き方です。
ステートとは、プログラムの中で起こりうる「ある状態」です。プログラムの最初から最後まで、いろいろな「ある状態」を経て処理が進んでいくイメージです。これらの状態を、何らかの基準、条件で遷移できるようにします。
プログラム全体の流れを考えるときに便利なモデルとしてフローチャートというものを聞いたことがある人もいるかもしれませんが、このステートマシンはそのようなフローチャート状の流れを図示できるような内容をプログラムに落とし込むための道しるべになります。
ただ、ここまで聞いても、「じゃあそれをどうやってGWDSのプログラムにするの?」と思いつくのは難しいと思います。状態をどう遷移するのか、ここで必要な知識が以下の内容です。
- ケースストラクチャ
- (タイプ定義された)列挙体
- Whileループ
- シフトレジスタ
これらの内容がわからない、覚えていないということであれば、この時点で一度復習することをオススメします。この先の内容はこれらがわからないと十分に理解できないと思われるためです。
ステートマシンをGWDSで実現する
さて、GWDSでステートマシンを実装するためにどのようにするのかを説明します。
まず、各状態である「ステート」はケースストラクチャで表現します。これは簡単、あるステートに対して一つのケースを割り当てます。でもケースストラクチャは条件を指定する必要がありますね、そこにタイプ定義された列挙体を使用します。
そして、これらのケースを色々と遷移するために、Whileループとシフトレジスタを使用します。シフトレジスタで列挙体を渡してやるんです。
Whileループの「N回目」のループで「あるステート(ケース)」を実行し、その次の「N+1回目」にどのステートを実行するかを決めてシフトレジスタでステートを指定する、そんな書き方になります。
こうすることで、「前のループであるケース(=状態、ステート)だったが、その処理の内容によって今度のループのケースが変わる」ことを表現できます。
ステートマシンの仕組みはこれだけです。
ステートマシンとは、各状態をケースストラクチャで実行するためのデータタイプとして列挙体を用意し、Whileループとシフトレジスタで列挙体の情報を更新することで状態を変える組み方、となります。
フローチャートを考えられるプログラムにはステートマシンが適用できることが多いです。この組み方だけでは実現できないプログラムというのもあるにはあるのですが、まずはこの考え方をマスターすることでプログラムを書く感覚に慣れればいいと思います。
実際のステートマシンプログラムの例
では、実際にステートマシンプログラムを書いてみることにします。とりあえずどんなもんか、を紹介するために、Web Applicationとしてはあまり意味がないですがフローがわかりやすい簡単なプログラム、「コラッツ予想」を例にとります。
まずは何をしたいか
コラッツ予想は、数学の未解決問題なのですが、内容は小学生で習う四則演算の知識で理解できます。ある正の整数Nを考えたとき、
- その数Nが偶数なら2で割る
- その数が奇数なら3倍して1足す
という処理を行い、これを新たなNとして、また上記の二択で計算を行う、ということを繰り返していきます。すると、最初のNがどんな値でも、ずっと繰り返すと最終的に1となる、これがコラッツ予想です。
なぜそうなるか?・・・少なくとも2022年の時点では数学者にも謎のようです。
このコラッツ予想で「Nから計算を経て最終的に1になる」までの過程をプログラムで追っていけるようにしたいと思います。
フローを考える
ではプログラムの中で処理がどのように進んでいくか、フローを考えてみます。ユーザー操作として必要そうなことは、最初のNを決める事くらいです・・・が、カウンタ用の数値を入力するための制御器も別途用意しておきます。これは何かというと、計算のしすぎを防ぐための上限を決めるための数値です。
コラッツ予想、どんなNでも1に到達するらしいのですが、Nの値によっては1にたどり着くまでの計算が膨大になります。そのため、(一見)プログラムが終わらないように見えるため、上限を設け、その上限に達したら何か警告を出すようにします。
また、ユーザーに結果を知らせる方法としては、1に到達するまでにどのようなルートを通ったかを示すためにグラフに表示させるようにします。グラフに表示させるためには、1になるまでの計算結果全てが配列に入っている必要があります。
Nや上限の数を決めて計算開始させるためのボタン、1になるまでに何回計算したかを表示させる数値表示器もつけます。既に上の図で出していますが、パネルは以下のようにしてみました。
あとは、どんな処理が必要かに応じて、ステートの数を決めていきます。ここは慣れがある程度必要ですが、頭の中でどのような処理をしてどのような判断を行うべきかを一つ一つ考えながら挙げていきます。
ということで、今回のステートマシンに必要な状態を以下のように考えてみました。なお、状態の分け方は一通りではないと思います。(括弧内は列挙体の項目名を表わしています)
- 初期化状態(init)・・・ある数Nおよび計算の上限Mを決めて、計算回数のカウンタをリセットする。
- 判断(judge)・・・計算の回数がM以下の時、ある数Nが偶数か奇数か判断する。もし計算回数がMのときには上限到達のステートへいく。ある数Nが偶数なら偶数計算のステートへ、奇数なら奇数計算のステートへいくようにし、もしNが1になっていたら終了のステートへ進む。
- 上限到達(limit)・・・上限に到達したことを知らせるメッセージを表示させる。
- 偶数計算(even)・・・ある数Nを2で割る。計算回数のカウンタを1増やし、判断のステートへ戻る。
- 奇数計算(odd)・・・ある数Nを3倍して1を足す。計算回数のカウンタを1増やし、判断のステートへ戻る。
- 終了(finish)・・・これまでの結果をグラフに表示させる。一定時間後、初期化状態に移動する。
これを、ステートマシンの図におとしこむと以下のようです。
ステートマシンの準備
フローも決まったので、実際のステートマシンを組むための準備を行います。ステートマシンに必要なものは、Whileループ、ケースストラクチャ、タイプ定義した列挙体、シフトレジスタだったので、まずはタイプ定義した列挙体を用意してからメインのindex.gviwebを構築していきます。
あとは各ステートを用意していきます。なお、以下から紹介するプログラムで同じ色のワイヤが複数表れることからそれぞれを区別するために、ワイヤにコメントをつけています(ワイヤを右クリックでCreate wire comment)。
まずは初期化の状態を以下のように考えてみます。
このinitケースは計算開始のボタンが押されないと内側のWhileループが終わらないと次に進みません。Whileループが終わるときに指定されているNと計算上限回数がシフトレジスタに渡されます。
そしてjudgeとなった列挙体がシフトレジスタに渡されているので、次のループではjudgeケースが実行されるはずです。
Nが偶数か奇数かは、2で割って、余りの数が0か1かで判断できます。
その結果によって、evenかoddの列挙体をシフトレジスタに渡すか、あるいは1になっていたら終了するという判断を行いますが、もし指定上限に達していたらlimitに移る、という処理も加える必要があります。これらの判断結果はそれぞれのブールの結果と選択関数で行っています。
even、oddの中身は次のようにしてみました。それぞれの計算を行っているのですが、どちらも共通して次にjudgeにまた進むようにしています。毎回の計算が行われるごとにjudgeされていく、という仕組みですね。
何度かの計算を経てそれでも1にならず指定回数の計算を行われる、つまりlimitの状態になったとします。ここでは、計算が指定回数に達しましたと表示していますが、その後にまた初期化の状態に戻るようにして、次の入力ができるようにしています。
finishのステートでは、上限計算回数に達する前に1になった場合だけ実行され、これまでの計算過程をグラフに表示するようにしています。
これでプログラムが完成しました。実際に動かすと、ちゃんと意図したとおりに動作することが分かります。
機能の追加に対応する
さて、最初に思い描いていたプログラムはこれでできたのですが、機能の追加をしたくなりました。
今のプログラムでは、計算全てが終わってからでないと計算過程がグラフに表示されません。しかしそうではなく、計算を行っている途中でデータをチャートに表示できるようにしたいときにどうすればいいでしょうか?
一つの作戦として、evenやoddの後にjudgeに進むのではなく、中間ステートのchartというステートを追加し、このステートでチャートに表示、その後にjudgeに進むことを考えます。
ここで、タイプ定義された列挙体が活躍します。
ステートを追加するとは、つまり列挙体の項目を増やすことを意味します。今既にこのプログラムには各ステートで列挙体を使用しています。
もし、これらの列挙体がタイプ定義されていない場合、そのすべての列挙体に対してchartという項目を追加する必要があります。一つでも「ヌケモレ」があった場合、正しく動かない可能性が出てきます。
しかし、列挙体がタイプ定義されている場合、そのタイプ定義の列挙体だけ項目を追加して保存すれば、これに紐づいたすべての列挙体が同時に変更されることになります。
このように、列挙体をタイプ定義しておくことで、確実にすべての列挙体に対してステートの変更が行え、上記の「ヌケモレ」の可能性を無くすことができます。
これが、ステートマシンにタイプ定義された列挙体を使用することの利点です。
さて、chartステートは以下のようにしてみました。
なお、このchartステートは、タイプ定義された列挙体に項目名を追加しただけでは、index.gviwebのケースストラクチャに表示されません。必ず、Create case for every valueで列挙体の変更をケースストラクチャに反映させるようにします。
また、ステート間を遷移する条件が変わる部分(今回で言えば、evenやoddからjudgeに移っていたのを、代わりにchartに移る)も忘れずに変更しておきます。
ケースストラクチャの外でステートを変える
少し変則的ですが、ステートを変えるための列挙体選択は、ケースストラクチャの中でないとやってはいけないということはありません。
例えば今回のプログラムで、中止ボタンを押すことで途中で計算を強制終了する仕組みを設けることができます。
当然、それ専用のステート(abort)をタイプ定義された列挙体で用意する必要があるのですが、問題はどうやって実装するかです。複数のステートにまたがって処理したい場合にはどうするか?
各ステートの中身に、中止ボタンが押されたときにはabortに移動するように仕組みを作っておくのも一つの手ですが、そうではなく、以下の図のように組むことができます。
もちろん、こういった組み方が適さない場合もあると思いますが、複数のステートにまたがって特定の判断をさせたい場合に有効になることがあるので、こういった組み方もあるんだくらいには覚えておいて損はないと思います。
とにかく、これでコラッツ予想の計算を行うプログラムが完成しました。
タイマーのプログラムをステートマシンで実装する
さて、上でステートマシンの例を紹介していましたが、もう一つ別の例を紹介します。
ケースストラクチャの回で紹介したタイマーのプログラムを、イベントストラクチャを使って書き直す、というものです。
実はステートマシンはイベントストラクチャとの相性がよく、「あるイベントが発生したら特定のステートに移動する」といったことができます。この仕組みを利用して、タイマーのプログラムをイベントストラクチャを用いて作ることができます。
各ステートの細かい説明はあまり行いませんが、以下では各ステートの中身だけ紹介していきます。なお、前回の記事で紹介したプロパティノードを使用しています。
まずは初期化のステートです。もしプロパティノードなどを使用する際には、この時点でリファレンスを用意して置き、シフトレジスタで他のステートに回すと全体として統一感が出ます。
次にイベント検出のステートです。今回イベントは一つしか用意する必要がなく、「測定開始」ボタンの値変更イベントです。このイベントが実行された時点で、設定時間として設定した時間と今の時刻を足し算します。
また、測定開始ボタンのDisabledプロパティにTRUE定数を入れています。この仕組みがあることにより、残り時間をカウントダウンしている間にユーザーが誤ってもう一度イベントをおこすといった可能性を防ぐことができます。
次に経過時間測定のためのステートです。
イベントストラクチャを経由する必要がないので、この部分のループが毎回回ることで残り何秒かの表示を狙い通りに出すことができます。
最後に終了のステートです。設定した時間が経過した後にまた次の時間経過タイマーを使用できるようにするため、Disabledプロパティに今度はFALSEを入力しています。
このステートの後にはまたinitのステートに行く必要はありません。またユーザーからのボタン押下を待つためにeventステートに戻ります。
イベント駆動型のステートマシンはフローチャートで表現されるプログラムを効率よく実行できるように組むことができる便利なテンプレートです。
なお、この記事を読んでくれている方の中には、LabVIEWを触ったことはないけれどGWDSを使用している、という方もいらっしゃるかと思います。そんな方は、今回紹介したステートマシンの手法はLabVIEWでも全く同じように適用できるので、もし今後LabVIEWを使う場面があったとしても身に着けた知識をそのまま活かすことができます。
今回の記事ではプログラムのデザインテンプレートの一つであるステートマシンを紹介しました。
プログラムを書く上で必要なループや条件分岐などの知識を身につけたからといって実際にプログラムを書けるかというとなかなか難しいと思いますが、こういった「プログラムをどのように組んでいくか」の考え方の見本となるテンプレートも覚えておくことで、まずはプログラムを書くことそのものに慣れていければどんどん楽しくなってくると思います。
さて、次回は他のデザインテンプレート・・・の紹介ではなく、GWDSに特有なHTMLやCSSなどの編集の話を扱おうと思います。これらおよびその次で取り上げるであろうJavaScriptの実装方法を知ることで、GWDSに元々備わっていることよりもさらに様々なことができるようになると思います。
もしよろしければ次の記事も見ていってもらえると嬉しいです。
ここまで読んでいただきありがとうございました。
コメント