
LabVIEW 2017からMalleable VIというものが実装されました。日本語では順応性VIと訳されています。個人的には汎用性のある処理を作るのに便利だと感じていて、Githubにいくつか便利そうなVIを置いています。
Github:https://github.com/taacranzk/TN_Malleable_VIs.git
ここではMalleable VIについて紹介したいと思います。
Malleable VIとは
Malleable VIは入力した配線のデータ型によって処理の内容を変えられるVI(?)です。順応性VIと訳されています。…順応性って何ですか?
…Polymorphic VI(多態性VI)と比較したときの特徴は以下の通りです。
- 作るVIは1個だけですむ。
- コネクタペーンも1パターンのみ。
- Type Specialization Structure(タイプ特化ストラクチャ)で場合分けする。
- インライン実行できないといけない。
- デバッグしにくい。
これを利用することによって、これまではできそうでできなかった処理、例えば、任意の配列に対して後ろn個を取り出す処理(PythonでいうArray[-1:]的な処理)などができるようになります。「任意の」というのが重要で、LabVIEWではサブVIに配線する時点で通常は配列のデータ型も決まります。なので、任意の型に対して同じような処理を1つのファイルで対応することはできませんでしたが、それを可能にしたのがMalleable VIです。これは配線した時点でとりあえずコンパイルを試みて、成功したらそのコードを採用し、うまくいかなかったらType Specialization Structureの次のケースでコンパイルを試す…といった感じだと思います。くわしくは公式のドキュメントやサンプルコードを見ていただければと思います。
VIのリスト
思いついたら随時追加していく予定です。思いつけばですが。
- Array Average:入力配列の平均値を計算します。配列の総和を計算してから要素数で割っているため、オーバーフローする場合は要修正。配列の次元は何でも可。
- Array Nearest Index:配列から入力値に最も近い要素を取得します。配列の次元は何でも可。配列の型はDoubleを想定。
- Array Same Size:2つの入力配列のサイズを比較します。2つの配列の型が異なる場合も対応可能です。配列の次元は任意ですが、2つの入力で同じである必要があります。
- Boolean to 0-1:ブールを0,1に変換します。入力のブールは任意の次元の配列でも対応可能。変換する型を指定できるようにしています。
- Close Front Panel:パネルを閉じます。入力するVIリファレンスは任意の次元の配列でも対応可能。このVIでは閉じるときに発生するエラーを無視します。エラーが入力されていてもパネルを閉じようとします。
- Format Accounting Number:数値入力を会計の表記(日本のもの、3桁ごとにカンマ区切り)に変換します。
- Generate Value Change Event at Current Value:現在の値のまま値変更イベントを発生させます。
- Get Current Timestamp:最大3つの配線が全て入力されたときのタイムスタンプを取得します。
- Get Tail of 1D Array:1次元配列から後ろn個だけを出力します。
- Random Number:乱数を発生させます。出力する型をType入力で指定できます。アルゴリズムは既存のPolymorphic VIのコピーペーストです。
- Set Named Number Default if Invalid:Named Number(列挙体やテキストリング)制御器の現在値が無効の値なら入力したデフォルト値に変更します。デフォルト値の無効確認は行いません。
例1:Get Current Timestamp
これは単に現在時刻を出力するだけのものです。これだけなら既存関数にありそうですが、既存のものでは出力端子しかないので、何か処理が終わった後の時刻を取得するにはフラットシーケンスを使う必要があります。毎回フラットシーケンスに現在時刻を取得する関数を入れるのは面倒ですが、かといって、処理が終わった後にどんな配線ができるかはプログラムによるので、従来のVIのようなあらかじめ入力の型を決めるものではうまく対応できませんでした。それがMalleable VIによってうまい具合に対応できるようになりました。

これがブロックダイアグラムとヘルプです。このブロックダイアグラムにおけるInput 1-3はバリアントになっていますが、実際は何でもいいです。一部未配線があってもいいです。例えば、Input 3にエラーを配線した場合、エラーが流れてきたときの現在時刻を出力し、エラーはそのままOutput 3に出力されます。
例2:Boolean to 0-1
これは入力したブールを数値に変換するものです。Falseなら0、Trueなら1に変換します。これも既存の関数がありますが、既存関数ではI16にしか変換できないので、そこからほかの型に変換できるようにしています。

ここではType Specialization Structureを使用しています。Type Specialization Sturctureでは、Typeの型を確認し、Assert Structural Type Match関数でしていた型と一致していればその処理を採用し、一致していなければ該当ケースでのVIは壊れ、次のケースで壊れないか確認します。これにより、指定した型で結果を出力できます。
Malleable VIの作り方の例
ライブラリを置いたついでに、Malleable VIの1つ作成します。ここではMalleable VIの例として次のものを作っていきます。
- 数値を会計であるような3桁のカンマ区切りの文字列に変換する。小数部分はそのまま。
- 例:123456.789 → 123,456.789
- 数値は固定小数以外の数値
- 負の数は▲で表す。例:-98765 → ▲98,765
LabVIEWには会計で使う数値で表現する機能はありそうでありません。なので、このような表示をするには自分で作る必要があります。数値の型が固定なら楽ですが、I32やDBLなどいろいろな型に対応するのは大変でした。このような例ではMalleable VIが便利です。
Malleable VIを作るには、NewからMalleable VIを選択します。

Ctrl+Nを押したくなりますが、それを我慢して上メニューのNewから選択します。そうすると、フロントパネルとブロックダイアグラムが開かれます。見た目は普通のVIですが、拡張子が「.vim」になっていて、Malleable VI向けのVIプロパティの設定がされています。具体的には、デバッグが無効、クローン事前割り当て再入実行、インライン実行有効の設定がされています。これらを変更してはいけません。

今回は、次の端子を持つVIを作ります。
- 入力:数値
- 出力:文字列
最初に配置する数値は整数でも浮動小数でもいいですが、今回はI32にしておきます。この処理を考える場合、整数と浮動小数では処理が若干変わってくると思います。こういった型によって処理が変わる場合、Type Specialization Structure(タイプ特化ストラクチャ)を使って場合分けします。これは、最初のケースから1つずつVIがコンパイル可能か確認し、コンパイルできるならその処理を採用し、できないなら次のケースでコンパイルを試す…というのを繰り返すものです。文章だけではよく分からないかもしれない(私は分からなかった)ので、実際に整数の処理を作ったものを見てみましょう。

これは、整数がI8のときの処理です。I8では最大値が1000未満のため、素直に文字列に変換するだけです。上の場合はケースがDeclinedとなっていますが、Assert Structural Type MatchにI8とI32という異なる型が接続されているためです。もし、I8が入力された場合はAssert ~ の部分が壊れなくなり、[0] Declinedになっているケースが[0]Acceptになり、この処理が実行されます。ほかの整数の場合も作っていきます。

このケースの番号が[2]となっています。[1]は数値がU8のときの処理で、I8の処理をコピーしてAssert Structural Type MatchにつないでいたI8整数をU8に置き換えただけのものになります。上のケースでは、Assert Integer Type.vimという標準のサブVIを使っています。これは、関数パレットのComparison(比較)→Assert Typeにあります。

Assert Integer Type.vimでは、入力が整数以外の型の場合にVIを壊すというものです。今回はType Specialization Structureのケース[0]でI8の処理、[1]でU8の処理をしているので、ケース[2]ではI8、U8以外の整数の処理をします。ブロックダイアグラム上では入力がI32になっているため、ケース[2]はAcceptedになっています。つまり、I32ではこの処理が実行されます。定数1000にCoerce to Typeがつながっていますが、これは1000を入力した整数の方に強制変換するためのものです。なくても一応処理できますが、あったほうが強制ドットを防ぐことができて効率的になります。ちなみに、I8、U8とその他の整数で処理を分けたのは、I8やU8では上限が1000未満で、1000を強制変換するとバグると考えたためです。
次に浮動小数についての処理を実装します。

…思ったより複雑になりました。最初に想定していた入力から2つ増えていますし。浮動小数の場合、小数点以下何桁表示するのか、小数部分の最後に0が続く場合に0を隠すか、小数点以下の表示が0の場合はどうするかを考えないといけませんでした。見通しが甘かったですが、まあ、Malleable VIで重要なのは、ここでも浮動小数かどうかを見て、Numericが浮動小数ならケース[0]から[2]がDeclinedで[3]の処理がAcceptedになってこの処理をするという点です。
Numericに入力された型が整数でも浮動小数でもない場合は、VIを壊す必要があります。確実にVIを壊す処理をケース[4]に作ります。

この場合、Numericがブールでかつ数値ならばこのケースが壊れずに実行となりますが、それはありえません。ケース[3]までDeclinedされた場合、このケースが残り、VIが壊れるというわけです。一見するとケース[4]はいらないような気もしますが、固定小数がNumericに入力された場合にケース[3]で実行されるかもしれず、VIを作った人が想定していないことになるかもしれないので、確実に壊すケースを入れると安心です。
最後にコネクタペーンを設定します。ここで使う制御器/表示器はすべてコネクタペーンに設定されていなければいけません。そうしないとVIが壊れたままになります。

これで完成です。テストする場合は、これを別VIからいろいろな型の配線をして試します。実行のハイライトやプローブなどのデバッグ手法は使えず、入力と出力を見るしかないので、バグがあると原因究明が難しいという欠点がありますが、汎用性は高いです。これも公開コードに追加しておきます。
まとめ
Malleable VIは使えると便利です。手間をかければPolymorphic VIでもいいですが、データ型の数だけVIを作るのは非常に大変です。汎用性のありそうな処理を思いついたらライブラリに追加していければと思います。

