コンテナ型(List, Map, Tuple)
qi Framework
でサポートしている3種類のコンテナ型について、
- 関数の引数として渡す際に用いる
QiAnyValue
型変数の作成法 - 関数の戻り値として受け取った
QiValue
からの適切なデータ取得法
をそれぞれ紹介します。
3種類のコンテナ型のうち、配列(List
)はユーザプログラマがもっとも頻繁に用いるコンテナ型です。辞書(Map)
が使われるケースも稀ですが存在します。タプルを表現するQiTuple
はサービスを自作する際などに利用します。
1. 配列(List)
配列型のデータ構造は最も頻繁に利用されるコンテナ型です。角度値やテキストの受け渡しに用いられるほか、(おそらくAldebaranのライブラリ整備手順など歴史的経緯と思われる理由から)辞書型データを利用すべき箇所でも一部のデータは配列によって表現されています。
1-1.QiAnyValue型の派生型としての配列
QiAnyValue
から派生した配列型のクラスはジェネリッククラスQiList<T>
です。T
はQiAnyValue
の派生型である必要があります。Baku.LibqiDotNet
には非ジェネリックなQiList
というクラスも存在しますが、これは静的クラスで、単にQiList<T>
を作成するファクトリメソッドを定義しただけのクラスです。
QiList<T>
を作成するにはQiList.Create
関数を用いるのが最も簡単です。入れ子リストを作るような複雑な例については関数の呼び出しに記載していますが、シンプルな1次元配列であればもっと簡単に、QiList.Create
関数を用いて作成できます。
var arr = QiList.Create(new[] { "Hello", "This is", "String Array" });
//arr: QiList<QiString>
QiList.Create
関数では組み込み型のIEnumerable
から適切なQiList
を作成するものを含めてオーバーロードを多数定義しており、通常はこれだけで1次元のQiList<T>
が作成可能です。
異なるアプローチとして、QiAnyValue
の派生型のIEnumerable<T>
(T
はQiAnyValue
の派生型)に対し拡張メソッドToQiList
関数を用いることでもQiList<T>
が得られます。
var numbers = new QiInt32[]
{
new QiInt32(3), new QiInt32(4), new QiInt32(5)
}.ToQiList();
QiInt32
はint
からの暗黙キャストをサポートしているため、上記のコードを次のようにも書けます。
var numbers = new QiInt32[]
{
3, 4, 5
}.ToQiList();
1-2. QiValueが保持するデータとしての配列
関数の戻り値としてQiValue
型変数を受け取り、その内容が配列であることをあらかじめ知っているとします。このときQiValue
から実際にデータを引き出す方法はいくつかありますが、QiValue
はプリミティブ配列への明示キャストをサポートしているため、int, double, string
のいずれかの1次元配列については.NETの配列として容易に変換できます。
var motion = session.GetService("ALMotion");
double[] angles = (double[])almotion["getAngles"].Call("Body");
QiValue
がキャストをサポートしていない複雑なデータを戻り値として受け取った場合は、よりプリミティブな処理が要求されます。例えば小数の2次元配列について内容を走査するコードは次のようになります。
QiValue response = someService["someMethod"].Call();
for (int i = 0; i < response.Count; i++)
{
QiValue row = response[i];
for (int j = 0; j < row.Count; j++)
{
QiValue elem = row[j];
double value = (double)elem;
//valueを使った処理
}
}
QiValue.Count
プロパティはQiValue
が実際に保持するデータがコンテナ型である場合にデータの個数を取得するために利用できます。配列やタプルではCount
の値を考慮したうえで整数値のインデックス指定により、個々の要素へのアクセスが可能です。この操作で得られるデータもまたQiValue
型です。上記の例ではresponse
変数の各要素row
が1次元配列であると仮定し、さらに内部のデータを取得しています。
なお、上の例に関してはより簡潔に書きたければ次のような記述も可能です。
QiValue response = someService["someMethod"].Call();
double[][] manyNumbers = Enumerable
.Range(0, response.Count)
.Select(i => (double[])response[i])
.ToArray();
//manyNumbersを用いた処理
NOTE: NuGetパッケージ2.0.0相当のライブラリにはQiList.Create
によく似た名前のQiList.CreateFrom
という関数もありますが、これは整備されていない関数なので使わないでください!次、あるいはその次のバージョンでこの関数は廃止される予定です。
2. 辞書(Map)
辞書(Map
)はキーと値のペアを保持するデータ構造です。qi Framework
で辞書を利用する場面は限られていますが、一部の重要な処理で利用されているため、動作を理解しておくのは非常に有意義です。
2-1. QiAnyValue型の派生型としての辞書
辞書のクラスはジェネリッククラスQiMap<TKey, TValue>
として定義されています。TKey
もTValue
もQiAnyValue
の派生型である必要があります。配列のときと同様、ファクトリメソッドを定義した静的クラスとしてジェネリックでないQiMap
も用意されています。
QiMap<TKey, TValue>
は(QiList<T>
より型の組み合わせが段違いに多いため)組み込み型からの生成をサポートしていませんが、拡張メソッドや暗黙キャストを組み合わせると最小限の煩雑さでインスタンスが生成できます。
QiMap<QiString, QiInt32> d = new Dictionary<QiString, QiInt32>()
{
["foo"] = 10,
["bar"] = 42,
}.ToQiMap();
ToQiMap
はIEnumerable<KeyValuePair<TKey, TValue>>
に対して動作する拡張メソッドとして定義されており、上記のようにDictionary
からの変換がサポートされています。
辞書に含まれるキー/値ペアの要素が少ない場合、可変長引数を取るQiMap.Create
関数を使った方法も比較的簡潔です。以下のように、キー値ペアを指定した分だけ受け入れて辞書を生成することが出来ます。
QiMap<QiString, QiInt32> d = QiMap.Create(
new KeyValuePair<QiString, QiInt32>("foo", 10),
new KeyValuePair<QiString, QiInt32>("bar", 42)
);
2-2. QiValueが保持するデータとしての辞書型
QiValue
型変数として受け取った辞書データから値を取り出すにはいくつかの決まった段階を経る必要があります。第一にQiValue.GetKeys()
関数でキー一覧の配列を保持したQiValue
を取得します。第二に、得られた各キーで実際に辞書式のアクセスを行うことで対応する値が取得できます。キーや値はいずれもQiValue
であるため、これらを.NETの値として扱うにはキャストを行います。
以下の例ではQiMap<QiString, QiInt32>
を生成してからそれをQiValue
に変換し、ふたたびQiValue
から内容のデータを取り出すサンプルを示しています。
var d = new Dictionary<QiString, QiInt32>()
{
["foo"] = 10,
["bar"] = 42
}.ToQiMap();
var dQiValue = d.QiValue;
var keysQiValue = dQiValue.GetKeys();
var keys = Enumerable.Range(0, keysQiValue.Count)
.Select(i => (string)keysQiValue[i])
.ToArray();
var values = Enumerable.Range(0, keysQiValue.Count)
.Select(i => (int)dQiValue[keysQiValue[i]])
.ToArray();
foreach(var k in keys)
{
Console.WriteLine(k);
}
foreach(var i in values)
{
Console.WriteLine(i);
}
出力例です。
bar
foo
42
10
NOTE: 上記のようなキー/値の一覧取得をおこなうコードは一般的であり、本来ならばBaku.LibqiDotNet
側でサポートすべき機能です。次のバージョンではQiValue
にMapKeys
, MapValues
, MapItems
というプロパティを追加して上記の操作を簡略化できるようにします(GitHubのdeveloper branchでは既にこの機能を公開しています)。
var d = new Dictionary<QiString, QiInt32>()
{
["foo"] = 10,
["bar"] = 42,
}.ToQiMap();
var dQiValue = d.QiValue;
//MapItemsプロパティはIEnumerable<KeyValuePair<QiValue, QiValue>>型なので
//foreach文を回すとDictionaryへのアクセスと同じようにで扱える
foreach (var pair in dQiValue.MapItems)
{
string key = (string)pair.Key;
int value = (int)pair.Value;
Console.WriteLine($"{key} : {value}");
}
//キー一覧を出力
dQiValue.MapKeys
.Select(k => (string)k)
.ToList()
.ForEach(Console.WriteLine);
//値の一覧を出力
dQiValue.MapValues
.Select(v => (int)v)
.ToList()
.ForEach(Console.WriteLine);
//これはダメ: OfTypeを通過しない
dQiValue.MapKeys
.OfType<string>()
.ToList()
.ForEach(Console.WriteLine);
3. タプル(Tuple)
タプルは複数のデータをひとまとめにする汎用的な方法を提供するデータ構造です。配列と類似していますが、要素ごとに型が異なっても構わないという重要な特徴を備えています。
3-1. QiAnyValue型の派生型としてのタプル
タプル型のクラスはQiTuple
です。上述の配列型や辞書型と異なりタプル型は非ジェネリッククラスですが、性質上はジェネリッククラスに近い特徴を持っています。
タプルのインスタンスはQiTuple.Create
関数によって生成できます。規約としてタプルを不変値(immutable)にすることは必須ではありませんが、誤操作を防ぐためには一度生成したタプルの要素を書き換えようとしない方が賢明でしょう。
var tuple = QiTuple.Create(
"Hello",
10,
true
);
Console.WriteLine(tuple.Dump());
出力例です。
Tuple[3]:
Dynamic
Dynamic
QiString:Hello
Dynamic
Dynamic
QiInt:10
Dynamic
Dynamic
QiInt:1
Dynamic
型でラップされていますが、データの内容が順に文字列"Hello"
と整数10
、そして整数化しているもののtrue
に対応する整数値である1
が順に格納されていることが分かります。
NOTE: 実は上記のようにタプルのデータがDynamic
でラップされる仕様は実装ミスで、本来は次のような出力を得られます(GitHubのdeveloper branchコードでは修正が行われており、実際に下記の出力を返します)。
Tuple[3]:
QiString:Hello
QiInt:10
QiInt:1
ただしDynamic
によるラップは有害になりづらいため、通常はあまり気にする必要はありません。
3-2. QiValueが保持するデータとしてのタプル型
QiValue
として受け取ったタプルの性質はQiList<T>
とほとんど変わりません。値の一覧を取得する場合Count
プロパティと整数値インデックス指定による値の取得を組み合わせた処理を記述することになります。
QiValue
としてタプルを受け取るもっとも典型的なケースはサービス自作の際に必要となる、QiObjectBuilder.AdvertiseMethod
関数の引数でのメソッド定義です。
var objBuilder = QiObjectBuilder.Create();
objBuilder.AdvertiseMethod("twice::i(i)", (sig, arg) =>
{
Console.WriteLine("twice was called");
int i = (int)arg[0];
return new QiInt32(i * 2).QiValue;
});
上記の例でarg
として渡されるのがタプルを保持しているQiValue
です。上の例では関数の引数として「単一の整数」を指定している(関数シグネチャの読み方についてはサービス自作を参照)ため、arg
は常に1要素のタプルであり、その要素は整数であることが保障されます。この例のようにタプルの内容は事前知識に基づいて決定できるため、タプルの内容を適切に処理することはそう難しくありません。