どうも、いつもプログラミング言語のことばかり考えているh_shimakawaです!
今回の記事では、私が作ったBinOp言語に新しく実装したオブジェクト指向らしい"条件分岐"と"繰り返し"について紹介します。 この記事を通じて、BinOpの新しい機能とその使い方を理解してもらえればと思います。
「そもそもBinOpってなんだっけ?」という人は以下のリンクを読んでみてください。 https://blog.hapins.net/entry/2023/12/24/120000
新しく実装されたBinOpの機能
- 新しい"条件分岐"の実装
- ちゃんとした"繰り返し"の実装
新しい条件分岐(ifメソッド)
コードの変更点
新しい条件分岐は以下のように書きます。
n := 1 "新しい条件分岐" (num < 5).if( "num in less than 5".print(), "num is greater than or equal to 5".print() ) "変更前の条件分岐 (現在は使えません )" if(num < 5, "num in less than 5".print(), "num is greater than or equal to 5".print())
いかがでしょうか? 組み込み関数だったifが、クラスメソッドとして実装されています。 「値自身が次に事項すべきコードブロックを選択する」ってのは初めて見ると新鮮ではないでしょうか? 「普段よりもっとオブジェクト指向してる」って感じが新鮮で楽しいですね!(強引な主張)
補足 BinOpのクラス構成
ところで「if()メソッドはどこに実装しているのだろう?」と思った人はいますか? 実はBooleanではなく、すべてのクラスに実装されています。 実はBinOpのクラス構成は以下のようになっています。
BinOpにはObjectクラスを継承したたくさんのクラスが存在するのですが、「偽」を表すのはNilクラスだけです。 そしてそれ以外のすべての値が「真」です。 なので、.if()メソッドはObjectとその配下の全てのクラスから生えてくる設計になってしまいます。
繰り返し(doWhile)
コードの変更点
今回初めて実装された"繰り返し"は以下のようになります。
"繰り返し用メソッドdoWhile" i := 0 r := fun(i<5).doWhile( i.print() i=i+1 )
繰り返し処理は関数オブジェクトのメソッドとして実装されています。 ループが繰り返されるたび関数オブジェクトが呼び出され、その結果が"真"である間、doWhileに渡された"処理"を実行します。 doWhileメソッドの戻り値は、これまでどおり"最後に評価した値"です。 サンプルコードで言うと、rに5(i=i+1の結果)が代入されます。
なぜ関数オブジェクトに実装??
「doWhileメソッドは真偽値に実装するべきでは??」って思った人いませんか? 直感的にそう感じると思います。 仮に実装してみましょう。 (もちろん、BinOpにBoolean型やtrue/falseは存在しないのですが、いったん"ある"ことで考えてみます。)
"doWhileを真偽値に実装してみた場合の架空のコード" v := 1 (v<5).doWhile( "any code"; v=v+1 ) "↓すると、 以下のようにtrue/falseのどちらかに評価されてしまう" true.doWhile( "any code" ); "→無限に繰り返す" false.doWhile( "any code" ); "→一度も実行されない"
うまくいきませんね。 必要な都度、条件式を(再)評価して欲しいので、関数オブジェクトで包んであげる必要があります。
doWhile実装前はどうしていたんだっけ?
再帰関数を使って繰り返し処理を行っていました。 ですが、「末尾再帰の最適化」が実装できず、たくさん再帰すると必ずstack over flowしていました。 「末尾再帰の最適化」の実装には時間がかかりそうなので、このメソッドでBinOpは実用に耐えうる"ちゃんとした"繰り返しを導入しました。 (気に入った構文を見つけたことだけがdoWhile採用の理由ではないのです・・・。)
着想はどこから得た? Pharoのはなし
google chatのもくもく会チャンネルにちょこちょこ進捗を書いていたのですが、最近オブジェクト指向言語Pharoを勉強しています。 PharoはSmalltalk系の言語で、C++、Java、C#の系列とは異なるオブジェクト指向の本家とも言える言語です。
Pharoはこの記事では紹介しきれないので、気になる人は以下のリンクに詳しいことが書いているので調べてみてください。 - Pharo by Example (日本語版) https://www.smalltalk-users.jp/Home/docs#h.p_ID_34
Pharoの条件分岐と繰り返し
さて、Pharoでは条件分岐と繰り返しを以下のように書けます。
"条件分岐のサンプルコード" number := 7. number >= 5 ifTrue: [ 'Number is greater than or equal to 5' ] ifFalse: [ 'Number is less than 5' ]. "繰り返しのサンプルコード" i := 0. [i < 5] doWhile: [ Transcript show: i; cr. i := i + 1 ].
似ていますね。というか、BinOpがまるパクりしたのがわかりますね。
BinOpの文法で許容できる範囲で採用しています。 (Boolean ifTrue: ifFalse:みたいなセレクターメソッドはBinOpの文法を直す必要があるので、そのまんまの採用はできませんでした!)
いい言語とは?
皆さんにとって"いい言語"とはなんでしょうか? 機能の多い言語、ミスのしにくい言語、習得しやすい言語などでしょうか?
時には"学びの多い言語"に取り組んでみるのはいかがでしょうか? 普段使っている言語からなるべく遠い言語を覚えると、いろんな文法要素や考え方に触れることができるので、成長が期待できます?
例えば、そう、Pharoとかどうでしょうか?