「C言語の関数呼び出しで引数は何個も渡せるのに返値は1個しかないのはどうしたことだどうせスタックに置く個数が変わるだけなのに」問題に関連する話に 「リターンアドレスが1個しかないのはどうしたことだ」問題があります。 どうにかしてみましょう。
String|NotFound get( Map<Int,String> map, Int key )
{
if( lowlevel_primitive_map_has_key(map, key) )
return lowlevel_primitive_unsafe_map_get(map, key); // 1個目のリターンアドレスに返る
return new NotFound("Error: the key \""+key+"\" not found"); // 2個目のリターンアドレスに返る
}
この例では文字飾りで2つのreturnを区別していますが、実際はほとんどの場合、 型を見て適切に呼び分ければ足りるんじゃないかと思います。returnのオーバーロードです。 これがベストかどうかはともかく、いずれにせよ、 呼ばれる側の書き方は return 先が複数に増えてもわりと簡単にいくらでも対応できます。 問題は呼び出し側。こんなのはどうでしょう。
void main()
{
String s = get(myMap, 42) ※1;
print( s );
※1: (e){
print( "エラーっぽい" );
e.printStackTrace();
}
print( "おしまい" );
}
1つめのreturnは、普通の言語の普通にリターンアドレスが1個の関数の場合と同じように、そのまま続きの式に戻る。 2つめ、3つめ、4つめの return がある場合は、関数呼び出し式のうしろに ※ と書いて飛び先を指定する。 対応する※は、同じ関数内のどこかに書いておく。 エラー処理なら脚注のように末尾に並ぶことが多かったりするのではないでしょうか。 (米印を使ってみたのはそういう理由です。)
void main()
{
String s = get(myMap, 42) ※:"ただしイケメンに限る";
// ※:expr は ※:(e){expr}; の略記
print( s );
print( "おしまい" );
}
その場にインラインで ※:
とすることで、続く式の開始アドレスがリターンアドレスになる。
つまりこの場合だと、エラー時のデフォルト値みたいなのをそこに書ける。
さらに、関数呼び出しより前にリターン先※を置くことを許してリトライ的なものを実現…は危険なことになるかなあ。
面白そうではあるんですけど。
String|NotFound decorate()
{
return "***" + (get(myMap, 42) ※:return) + "***";
}
first class return と組み合わせる。 ジャンプ先としてreturnオブジェクトを指定することで、エラーを上へフォワーディングしたいときにはそうします。 そもそも※を省略したときに、型のあうreturnがあれば勝手に補うくらいで良さそうかも。
考察といいますか、ネタ元は Multi-Return Function Call (Shivers&Fisher, 2006) です。他にも色々ありそう。いつものように「それ○○でできるよ!」募集中です。
例として使ったコードからおわかりのように、複数のリターンアドレスを設けるというのは、 Javaの検査例外や、Maybe型 / Option型 のようなエラー状態になりうるオブジェクトを返す方法や、CPS(継続渡しスタイル)に、 とても似たところがあります。その辺との比較考察がいろいろネタ元の論文でなされていますので皆様読むとよいであります。
自分としては、複数の出口を関数に持たせたいときに、 「例外のような長距離ジャンプのための機構を用いるのは意図と違う気がして違和感。 あと、tryとcatch(的なもの)でブロックスコープに縛られるのがしょっちゅう邪魔」 というのと 「制御フローを制御したいのに一旦Maybe等々のようなデータにエンコードするのは迂遠な気がするなあ」 という感覚がある、くらいですね。
今回のエントリを書くにあたって気になったのは、この機能そのものの是非はさておき、 実現するとしたらどういう構文がベストかなー、というところでした。 むしろ、十分に良い構文を見つけられればそれはきっと良い機能だ!
元ネタの論文はλ計算の最小限の拡張なので、 forwarding機能こそあれ、 基本的には複数のreturn先を一カ所に横並びに並べるというシンプルな構文になっています。 これは不便だなあ、と思って、手続き型っぽい言語でもっとフリーに返り先を配置できるように考えてみたのが、 今回の案です。さてさて。
return
のオーバーロードの件は、型で解決できるときは省略できるというだけで、
int|int
みたいな返値型にしたいときは、おとなしく
return[0]
と return[1]
みたいなのを使えばよいんじゃないかと思います。
呼び出され側は本当にどうとでも書きようがあるので、あんまり深く考える気がない…
それ FORTRAN で がビンゴっぽいですね! FORTRAN は分岐やGOTOに色々あるのは聞いたことがあったけど、 こんなのもあるのか!面白いなあ。関数でできないのはなんでなんだろう。
PRML(パターン認識と機械学習) Hackathon #1 行ってきました。→ 成果物 (moil.d)
あんまりこっち方面を勉強したことがなかったので、 せっかくだからと、 この一週間くらいPRMLを読み込んでそれっぽいネタを探そう…と思って当日会場についてからもそうしてたんですが、 とりあえず一番シンプルなパーセプトロンを実装をしてみた辺りで何も面白いネタがこれ以上思いつかん! っと力尽きかけたので、結局、PRMLと関係なくとにかく機械で学習っぽいものならなんでもいいやと方向転換しました。 敗北気味です。パーセプロトンは異様にシンプルで感動しました。
やったのは "Learning Regular Languages Using Nondeterministic Finite Automata" の MOIL アルゴリズムの実装です。
"aaa", "abb", "aaaabbbb", "abbbbb", "bbb"
にはマッチして
"baa", "bab", "bbbbbaaaaaa", "abba"
にはマッチしない正規表現プリーズ、と適当に正負の例を与えたら、
学習して /a*b*/
(という正規表現に相当する内部表現)を返してくれるようなものができました。
あるいは
"zzz@zzz.zzz", "bbb@ccc.aaa", "ccc@bbb.aaa", "prml@example.com"
はOKで
"aaabbbccc", "aaabbb.ccc", "aaa@bbbccc", "aaa.bbb@ccc"
はダメ、と指示すると /[a-z]*@[a-z]*\.[a-z]*/
が出ます。
ダメな例として "@."
とかを教えたげておくと、
多分 *
が +
になります。
…というデモをしたら shinh さんにすかさず突っ込まれたんですが、
MOIL だと正の訓練データにある文字しか学習してくれないので、本来はこの訓練データだと
ki@kmonos.net
みたいなのにはマッチしない正規表現になっちゃいます。
今回は、ものすごいアドホックな回避策として、
正の例の中に z
という文字を見たら [a-z]
の全パターンが例として来たと思って学習してます。
これは、実際に使うときにはこういうことをやらずにちゃんと大量のデータを与えてあげればいいのかな。
正の例1個目から、それだけにマッチするオートマトンを作る → 負の例に抵触しない範囲でオートマトンの状態と状態をまとめまくる → 正の例2個目から、それだけにマッチするオートマトンを作って、さっきのにくっつける → 負の例に抵触しない範囲でオートマトンの状態と状態をまとめまくる → … と、まあそうですよね、という感じの実装です。ソースは割とかなり酷いので気にしないで下さい。
今年も Google Code Jam が開かれるそうです。
前回より最終ラウンドに進める間口がだいぶ狭くなっているようですが、進出目指して頑張りたいところ。 木曜の朝8時から24時間が予選ラウンドで、終了直前まで参加登録可能っぽいので、 みなさまお時間あれば是非是非参戦してみませんか! 出題される3問のうち、1問でもちゃんと解けた全員が通過だそうな。 使う言語は自由。
FLTVのプレゼン で例にあげた a%(2||3)==0
について、きむら(K) さんに
「Perl 6 の ジャンクション
はどうでしょうか」と情報いただきました。不勉強なせいで知らなかったのですが、おお、これは素晴らしい!
ビット演算を追い落として &
と |
を奪い取らせるというのは英断だなあ。
セマンティクスは mame版 DelegateMap
に似ていて、複数の値に対して並行して演算を適用していく感じ。Perl 6 で言語レベルのサポートになるので、
関数引数にジャンクションを渡していると自動で複数回の関数呼び出しに展開される。
これ、特に言及無いけど、ユーザー定義のジャンクションはPerlレベルで作れるのかなあ。
「ちょうど2つが」 や 「過半数が」 みたいなの作りたい。
あと、"In particular, threading junctions through conditionals correctly could involve continuations"
と、やり過ぎると継続が要ることにも触れられているんだけど、
その先に書かれている設計がいまいちよく理解できてない。
一度ちゃんと Perl 6 の仕様読んでみないといけないなあ。