プログラミング言語 Boo チュートリアル(下書き版) by mei. 1. Booとは何か? BooはPythonに影響を受けた、CLI(.NETもしくはMono)をターゲットにしたプログラミング言語です。シンプルで習得しやすいPythonの文法でプログラミングができ、.NET言語なのでC#/VB.NET等で作成したライブラリを呼び出すことも、Booで作成したライブラリをC#/VB.NETへ公開することも出来ます。Booにはコンパイラだけでなく、インタプリタも用意されているため、ちょっとしたプログラムなら手軽に開発、実行が出来ます。また、対話型シェルもありますので、ライブラリの調査、テストをする際に役に立つでしょう。 2. 対話型シェルを使う Booがインストールされており、パスが通っている環境なら、 > booish とコマンドを入力すれば、対話型シェルが使えます。ちなみにboocがコンパイラ、booiがインタプリタです。booishはインタプリタのシェル(sh)ということですね。 booshが起動すると、 >>> このようなプロンプトが表示されます。簡単な計算をさせてみましょう。 >>> 1 + 2 3 このように結果が返ります。結果はプロンプトのような>>>はつかないのですぐ分かります。 複数の行からなる構文の場合は、...という二次プロンプトが表示されます。例として次のif文を見てください。 >>> a = 10 10 >>> if a >= 10: ... print "OK" ... OK ifは複数の行からなるので、二次プロンプトが続きの行を催促します。何も入力せずに改行すると終了し、構文が実行されます。 3. Booを試してみる 対話型シェルが使えるので、シェル上で色々動かしてみましょう。 3.1 数 >>> 2 + 2 4 >>> 2 + 2 # これはコメント 4 >>> 2 + 2 // これもコメント 4 >>> 2 + 2 /* これもコメント */ 4 C#と同じく、=を使って変数に値を代入します。 >>> a = 10 10 複数の変数に同時に値を代入することも出来ます。 >>> x = y = z = 10 10 >>> x 10 >>> y 10 >>> z 10 浮動小数点もサポートされています。 >>> 1.2 + 3.4 4.6 また、最後に評価された値は、シェル上では変数_に格納されています。 >>> _ * 2 9.2 3.2 文字列 もちろん、数だけでなく文字列も扱えます。文字列は、シングルもしくは、ダブルクォートで括ります。 >>> 'Hello, World' 'Hello, World' >>> "Hello, World" 'Hello, World' 文字列の連結には+を使います。 >>> hello = "Hello" + ", " + "World" 'Hello, World' 文字列はインデックス表記を使えます。 >>> hello[0] H ただし、Booの文字列(正確にはBCLのStringクラス)は変更不可なので、次のような書き換えを行おうとするとエラーが発生します。 >>> hello[0] = 'h' ----^ ERROR: Property 'System.String.Chars' is read only. また、インデックス表記だけでなく、スライス表記によって部分文字列を取り出すことも出来ます。インデックス表記は[開始:終了]と:で区切り、終了は境界を含みません。この場合ですと、[開始, 終了)という範囲になります。 >>> hello[1:5] 'ello' スライスには省略値があります。開始を省略すると0、終了を省略すると文字列の長さ(つまり、文字列の最後の要素の次)となります。 >>> hello[:2] 'He' >>> hello[2:] 'llo, World' また、スライスの開始、終了には負の値を使用出来ます。その場合、文字列を後ろから数えた位置となります。 >>> hello[:-1] 'Hello, Worl' -1は文字列の先頭を0として後ろに1文字なので文字'd'を指します。スライスでは終了は境界を含まないので最後の文字'd'が削除されます。これを利用すると改行'\n'を含む文字列から改行を削除することも簡単に出来ますね。 3.3 リスト Booで最もよく使われるコレクション型です。コンマで区切ったリストを鉤括弧で括ることで書き表します。 >>> c = [1, 'two', 3.0, "four"] [1, 'two', 3, 'four'] リストは文字列と違って変更可能です。 >>> c[2] = 3.3 >>> c [1, 'two', 3.3, 'four'] 文字列で使用したスライスはリストでも使用できます。 >>> c[2:] [3.3, 'four'] リストへの追加にはAddメソッドを使用します。 >>> c.Add(5) [1, 'two', 3.3, 'four', 5] >>> c.Add('six') [1, 'two', 3.3, 'four', 5, 'six'] AddUniqueメソッドを使うと重複した値は追加されません。 >>> c.AddUnique(3.3) [1, 'two', 3.3, 'four', 5, 'six'] >>> c.AddUnique(7.7) [1, 'two', 3.3, 'four', 5, 'six', 7,7] 要素の削除にはRemoveを使用します。 >>> c.Remove(1) ['two', 3.3, 'four', 5, 'six', 7,7] >>> c.Remove(5) ['two', 3.3, 'four', 'six', 7,7] RemoveAtで位置による削除が出来ます。 >>> c.RemoveAt(2) ['two', 3.3, 'six', 7,7] 組み込み関数lenでリストの要素数が取得できます。 >>> len(c) 4 Clearで全削除です。 >>> c.Clear() >>> len(c) 0 3.4. プログラミングっぽいこと 単純な計算や、文字列操作を見てきましたが、Booはプログラミング言語なのでもちろん、もっと複雑なこともできます。試しにFibonacci級数を求めてみましょう。 >>> a, b = 0, 1 # a = 0, b = 1を同時に行う 1 >>> while b < 10: # while文 条件を満たす間、ループする ... print '*', b # bの内容を表示 ... a, b = b, a + b # 次のFibonacci数を求める ... * 1 * 1 * 2 * 3 * 5 * 8 13 最後の13は、インタプリタが最後に評価した値です。求めたFibonacci数と区別するために、計算した値には'*'をつけています。 ところで複数行からなる構文では字下げを行っていますが、これは見やすくするためではなく、ブロック(C#で{...}で括ることと同様)を構成しているのです。例えば、Booは同一の字下げを行っているところをブロックと見なしますなので、 >>> while b < 10: ... print '*' b ... a, b = b, a + b ... と書いてしまうと、whileブロックはprint関数までとなり、ひたすらbの値が出力されることになってしまいます。さらっと書いてしまいましたが、Booではとても重要なことなので忘れないようにしてください。 4. フロー制御及び、関数 4.1 if文 条件による分岐をさせます。分岐が増えると見難くなりますが、現状、if-elifを使うことになります。将来のバージョンではgiven-when(C#におけるswitch-case)がサポートされる予定です。 >>> if x < 0: ... print 'Negative' ... elif x == 0: ... print 'Zero' ... else: ... print 'Positive' 4.2 for文 要素に対する反復処理を行います。C#のforでは無く、foreachに相当します。 >>> cities = ['Tokyo', 'Sapporo', 'Osaka'] ['Tokyo', 'Sapporo', 'Osaka'] >>> for city in cities: ... print city, len(city) ... Tokyo 5 Sapporo 7 Osaka 5 4.3 range関数 C#のforのようにループカウンタを使ってループさせる場合には、range関数を使うと便利です。 >>> cnt = len(cities) 3 >>> for i in range(cnt): ... print cities[i], len(cities[i]) ... Tokyo 5 Sapporo 7 Osaka 5 この例ですと、あえてループカウンタを使う必要はありませんね。ところで、range関数の戻り値は何でしょうか? ちなみにPythonではrange(5)とかすると、0〜4のリストが返されました。Booの場合は、 >>> r = range(5) Boo.Lang.Builtins+RangeEnumerator とRangeEnumeratorが返されます。名前から想像できるようにIEnumearatorインタフェースを実装しているので、 >>> r.MoveNext() true >>> r.Current 0 >>> r.MoveNext() true >>> r.Current 1 このように呼び出すことも出来ます。 4.4 break文とcontinue文 C#のbreak/continueそのまんま。 >>> x = 0 0 >>> while true ... ++x # xをインクリメント ちなみにx++はサポートしていない ... if x < 3: ... continue # 次の反復処理へ ... if x > 8: ... break # ループを終了する ... print '*' * x # '*'をx個表示 ... *** **** ***** ****** ******* ******** 9 4.5 pass 何もしない文。文が構文上必要だけど処理を行う必要がない場合に使います。プレースホルダ。 >>> if x > 10: ... pass ... else: ... print x とか。 4.6 関数を定義する 先ほど書いたfibonacci級数を求めるコードを関数にしてみましょう。 >>> def fib(n as int): ... a, b = 0, 1 ... while b < n: ... print '*', b ... a, b = b, a + b ... >>> fib(10) * 1 * 1 * 2 * 3 * 5 * 8 このように関数定義は def 関数名(引数): 関数本体 のように書きます。もし、戻り値がある場合は、 def 関数名(引数) as 型: とします。 4.7 匿名関数 Booでは匿名関数をサポートしています。 >>> def make_incrementor(n as int): ... return def (x as int): ... return x + n ... >>> f = make_incrementor(42) Input11Module+___closure1.Invoke >>> f(0) 42 >>> f(1) 43 このようにdefの後に関数名を指定しなければ匿名関数となります。また、匿名関数がブロックを必要としなければ、次のように書くこともできます。 >>> def make_incrementor(n as int): ... return { x as int | x + n } ... 5. データ構造 リスト、ハッシュなどのビルトインデータ型について説明します。 5.1. リスト型について リスト型については3.3でもふれましたが、メンバについてもう少し詳しく見てみます。 メソッド - List Add(object item) 要素をリストの最後に追加します。 >>> list = [] [] >>> list.Add(1) [1] >>> list.Add("two") [1, 'two'] - List AddUnique(object item) 要素がリストに含まれて居なければ、リストの最後に追加します。 >>> list = [] [] >>> list.AddUnique(1) [1] >>> list.AddUnique(1) [1] >>> list.AddUnique("two") [1, 'two'] - void Clear() リストの要素をnullで初期化し、要素数を0とします。キャパシティはそのままです。 >>> list = [1, 2, 3] [1, 2, 3] >>> list.Clear() >>> list [] - List Collect(Predicate condition) 条件を満たす要素の集合をリストとして返します。Predicateにはobjectを引数としてboolを返す関数を指定します。 >>> list = [i for i in range(10)] [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> sub = list.Collect({x as int | x < 5}) [0, 1, 2, 3, 4] - List Collect(List target, Predicate condition) 条件を満たす要素をtargetに追加します。戻り値はtargetとなります。 >>> list = [i for i in range(10)] [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> sub = ['one', 'two', 'three'] ['one', 'two', 'three'] >>> list.Collect(sub, {x as int | <= 3}) ['one', 'two', 'three', 0, 1, 2, 3] - bool Contains(object item) itemがリスト中に存在すれば、trueを返します。 >>> list = ['one', 'two', 'three'] ['one', 'two', 'three'] >>> list.Contains('one') true >>> list.Contains('four') false - bool Contains(Predicate condition) 条件を満たす要素がリスト中に存在すれば、trueを返します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list .Contains({x as int | return x < 3}) true >>> list .Contains({x as int | return x >= 10}) false - void CopyTo(Array target, int index) リストの要素を配列のindex以降へにコピーします。配列に領域が足りない場合は例外が発生します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> arr = array(range(0, 20)) (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19) >>> list.CopyTo(arr, 10) >>> arr (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - List Extend(IEnumerable enumerable) リストに要素を追加します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list.Extend(range(10, 20)) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] - object Find(Predicate condition) 条件を満たす最初の要素を取得します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list.Find({x as int | return x < 7}) 0 - List GetRange(int begin) begin以降の要素を取得します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list.GetRange(3) [3, 4, 5, 6, 7, 8, 9] - List GetRange(int begin, int end) [begin, end)の範囲にある要素を取得します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list.GetRange(3, 7) [3, 4, 5, 6] - int IndexOf(Predicate condition) 指定した条件を満たす最初の要素のインデックスを取得します。見つからない場合は-1を返します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list.IndexOf({x as int | return x > 5}) 6 - int IndexOf(object item) 指定した要素のインデックスを取得します。見つからない場合は-1を返します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list.IndexOf(7) 7 - List Insert(int index, object item) 指定した位置に要素を挿入します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list.Insert(5, 4.5) [0, 1, 2, 3, 4, 4.5, 5, 6, 7, 8, 9] - string Join(string separator) 要素をseparatorで区切って文字列にします。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list.Join('|') '0|1|2|3|4|5|6|7|8|9' - List Multiply(int count) リストをcount分繰り返したリストを返します。 >>> list = ['one', 'two', 'three'] ['one', 'two', 'three'] >>> list.Multiply(3) ['one', 'two', 'three', 'one', 'two', 'three', 'one', 'two', 'three'] - object Pop() リストの最後の要素を取り出します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list.Pop() 9 >>> list [0, 1, 2, 3, 4, 5, 6, 7, 8] - object Pop(int index) indexで指定した要素を取り出します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list.Pop(0) 0 >>> list [1, 2, 3, 4, 5, 6, 7, 8, 9] - List Push(object item) リストの最後に要素を追加します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> list.Push(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - List Remove(object item) リストから要素を削除します。 >>> list = ['one', 'two', 'three'] ['one', 'two', 'three'] >>> list.Remove('two') ['one', 'three'] - List RemoveAt(int index) indexで指定した要素を削除します。 >>> list = ['one', 'two', 'three'] ['one', 'two', 'three'] >>> list.RemoveAt(1) ['one', 'three'] - List Sort() リストをソートします。 >>> list = ['one', 'two', 'three'] ['one', 'two', 'three'] >>> list.Sort() ['one', 'three', 'two'] - List Sort(Comparer comparer) comparerで指定した順にソートします。comparerには2つのobjectを引数にとってintを返す関数を指定します。 >>> list = ['one', 'two', 'three'] ['one', 'two', 'three'] >>> list.Sort({x as string, y as string | return y.CompareTo(x) }) ['two', 'three', 'one'] - Array ToArray(Type targetType) リストをtargetType型の配列に変換します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> arr = list.ToArray(typeof(int)) (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) >>> arr.GetType() System.Int32[] - object[] ToArray() リストを配列に変換します。 >>> list = [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> arr = list.ToArray() (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) >>> arr.GetType() System.Object[] プロパティ - Count リストの要素数。 5.1.1. リストをスタックとして使う リスト型のメソッドを使用するとリストをスタックとして使えます。 >>> stack = [3, 4, 5] [3, 4, 5] >>> stack.Push(6) [3, 4, 5, 6] >>> stack.Push(7) [3, 4, 5, 6, 7] >>> stack.Pop() 7 >>> stack [3, 4, 5, 6] >>> stack.Pop() 6 >>> stack.Pop() 5 >>> stack [3, 4] 5.1.2. リストをキューとして使う リストをキューとして使うこともできます。 >>> queue = [3, 4, 5] [3, 4, 5] >>> queue.Push(6) [3, 4, 5, 6] >>> queue.Push(7) [3, 4, 5, 6, 7] >>> queue.Pop(0) 3 >>> queue.Pop(0) 4 >>> queue [5, 6, 7] 5.1.3. ビルトイン関数との組み合わせ ビルドイン関数"map(function, enumerable)"はenumerableのすべての要素xに対し、function(x)を行った結果をenumerableとして返します。例えば、1〜9を二乗した値のリストを作成するには次のようにします。 >>> list = List(map({x as int | return x * x}, range(1, 10))) [1, 4, 9, 16, 25, 36, 49, 64, 81] 5.1.4. リスト内包表記 前節の1〜9を二乗したリストはリスト内包表記を使うと次のように簡潔に書くことが出来ます。 >>> list = [x * x for x in range(1, 10)] リスト内包表記はそれだけでなく、次のような柔軟性もあります。 >>> list = [x * x for x in range(1, 10) if x % 2 != 0] [1, 9, 25, 49, 81] >>> list = [x * x for x in range(1, 10) unless x % 2 == 0] [1, 9, 25, 49, 81] 5.2. 配列 Booの配列型はBCLのArrayをそのまま使用しています。BCLのマニュアルを参照ください。配列はビルトイン関数arrayや、要素を,(カンマ)で区切って括弧で括ることで作成することが出来ます。 >>> arr1 = array(typeof(int), 10) # int型、要素数10の配列を作成 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) >>> arr2 = (1, 2, 3, 4, 5) # 要素1, 2, 3, 4, 5からなる配列を作成 (1, 2, 3, 4, 5) 5.3. ハッシュ BooのHash型はBCLのHashtableを継承したものです。BCLのマニュアルを参照ください。ハッシュはKey:Valueの組を,(カンマ)で区切って中括弧で括ることで作成することが出来ます。 >>> hash = {'one': 1, 'two': 2} {'two': 2, 'one': 1} >>> hash['one'] 1 >>> hash['two'] 2 ハッシュへの追加は、キーを指定して代入するだけです。 >>> hash['three'] = 3 >>> hash {'three': 3, 'two': 2, 'one': 1} すべての要素に対する処理は次のように行います。 >>> for e in hash: ... print e.Key, e.Value ... three 3 two 2 one 1 また、キー、値それぞれの列挙は次のようになります。 >>> for k in hash.Keys: ... print k ... three two one >>> for v in hash.Values: ... print v ... 3 2 1 6. ソースファイルとアセンブリ 6.1. ソースファイルからの実行 今まで、booishシェルでコードを打ち込んできましたが、シェルを終了するとすべての内容が消えてしまいます。より長いプログラムを作成するにはエディタでファイルにコードを打ち込んで保存することになります。試しに、フィボナッチ級数を求めるプログラムを作成し、fib.booというファイルに保存してみましょう。 -- fib.boo ここから def fib(n as int): # n以下のフィボナッチ数を表示する a, b = 0, 1 while b < n: System.Console.Write(b + " ") a, b = b, a + b print def fib2(n as int): # n以下のフィボナッチ数のリストを返す result = [] a, b = 0, 1 while b < n: result.Add(b) a, b = b, a + b return result fib(1000) print fib2(1000) -- fib.boo ここまで ソースファイルをコードを実行するには以下のコマンドを使用します。 > booi fib.boo fib.booは実行されて、次のような結果が表示されます。 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987] 対話型シェルで打ち込んでいた時とは違って、booiを使うことでfib.booをいつでも実行できるようになりました。 6.2. 関数の再利用 関数fib、fib2はfib.boo内で使用出来ますが、他のプログラムからも呼び出したい場合、どうすればいいでしょうか? fib.booからコピー&ペーストで関数を持ってくることも出来ますが、もし、不具合があった場合にはコピーした箇所すべてを修正することになるので好ましくありません。Booでは関数をダイナミックリンクライブラリ(以降、DLL)として再利用します。DLLを作成するにはbooc.exe(Booコンパイラ)でコンパイルすることになります。コンパイルといっても難しくは無く、次のようなコマンドを実行すればいいです。 > booc -t:library fib.boo -tオプションで作成するアセンブリの種類をDLLにしています。コンパイルが成功するとfib.dllが作成されます。 ソースファイルが複数ある場合は、スペースで区切って列挙すればいいです。 > booc -t:library foo.boo bar.boo コンパイルが成功するとfoo.dllが作成されます。ソースファイルが複数ある場合は最初のソースファイル名でDLLが作成されます。もし、ソースファイルとは異なるDLL名を付けたい場合は、-oオプションで出力ファイル名を明示的に指定することもできます。 > boo -t:library -o:mydll foo.boo bar.boo この場合、mydll.dllが作成されます。 6.3. 名前空間とimport DLLによって他の人が作った関数を利用出来ることは良いことですが、それによって新たな問題が発生することがあります。名前の衝突です。例えば、他の人が作ったライブラリ、your.dllがあったとします。ここに含まれているfib関数を使いたいのですが、このライブラリにはcalc関数も含まれていました。ところが、自分のライブラリ(my.dll)でもcalcという名前の関数を作っていたら、関数名が衝突してどっちのcalcか区別がつかなくなってしまいます。 my.dllとyour.dllを使ったアプリケーションをコンパイルすると次のようなエラーが発生します。 BCE0004: Ambiguous reference 'calc': Your Module.calc(), MyModule.calc(). 1 error(s). どちらかが関数名を変更すればとりあえず、問題を解決できますが、影響が大きすぎて変更できない場合も当然あります。このような問題を解決するために、名前空間が用意されています。 -- your.boo ここから # your.dll namespace Your def calc(): print "your.calc" -- your.boo ここまで -- my.boo ここから # my.dll namespace My def calc(): print "my.calc" -- my.boo ここまで このようにファイルの先頭に"namespace 名前空間名"を入れることで、関数名の衝突を避けられます。また、これを呼び出すapp.booは次のようになります。 -- app.boo ここから Your.calc() My.calc() -- app.boo ここまで このように"名前空間.関数"という書き方になります。いちいち"名前空間.関数"と打ち込むのが面倒な場合は、importを使うことで名前空間を指定しなくても良くなります。 -- app.boo ここから import Your calc() # Your.calcが呼ばれる My.calc() -- app.boo ここまで もちろん、名前空間My、Your両方をimportしてしまうと関数名は衝突してしまいます。 6.4. コンパイルについて コンパイルで作成できるのはDLLだけでなく、EXE(実行モジュール)も作成することが出来ます。booiで実行する代わりにEXEに変換すれば、配布時にインタプリタを含める必要がなくなるので、最終的にはコンパイルすることになります。たとえば、fib.dllを使ったアプリケーションとしてapp.booを開発したとします。開発時には、 > booi -r:fib.dll app.boo このように、インタプリタでテストしたりします。コーディングが終われば、コンパイルを行ってEXEに変換します。 > booc -t:exe -r:fib.dll app.boo EXEになるとインタプリタは不要なので、 > app.exe と実行することが出来ます。 7. ファイル操作 8. 例外処理 9. クラス 10. ビルトイン関数 11. その後は? -- 2004/12/23 初版 2004/12/26 5、6章を追加