2018年2月17日土曜日

オレオレ言語、Expressoについて・・・骨格解説編

こんにちは、はざまです。今回も前回に引き続き、自作言語Expressoの解説をしていこうと思います。

以下、まずはいくつか特筆すべきと思われるExpressoの機能を紹介します。
まず、Expressoでは、組み込み型でvectorやdictionaryがサポートされています。これらを生成するリテラルが用意されていますし、多少コンパイルでも特別扱い(実体は、それぞれSystem.Collections.Generic.ListとSystem.Collections.Generic.Dictionaryですが)されます。ただ、今の所は、実装上の問題により、パターンマッチの対象にはなっていないのですが……
他に組み込み型関連では、intseqという型があります。intseqとは"integer sequence"の略で、これはPythonで言うところのxrange型やRustのRange型と同様、整数列を生成するジェネレータです。残念ながら、int(32ビットの整数)の範囲の整数しか扱えないのですが、いわゆるCのような旧式のfor文が存在しないExpressoにおいて、カウントアップなどを行う際に多用される型です。vectorやarrayなどに作用して、整数列にマッチする要素だけを取り出すiterator(.NET用語だと、enumerator)も生成できます(sliceと呼ばれる)。
Expressoには、Rustにも存在するmatch文があります。これは、最近はやりのパターンマッチを行う文法要素で、各種オブジェクトの分解や、リテラル値とのマッチングを行います。tupleパターンとのマッチ程度しか想定していませんが、一応変数宣言(let文var文)でも、パターンが使用できるようになりました。

いくつか特筆に値する機能を見たところで、皆様も気になっているかもしれないExpressoの原理について解説しましょう。まず、現状、コンパイラは純C#製です。レキサ、パーサー、アナライザ、コード生成、全部C#で完結しています。吐かれるバイナリは、C#のコンパイル後の表現であるIL形式ですし、パーサージェネレータもC#のものを使用しています。この部分もC#を選んだ理由の一つに挙げられるでしょう(式木と呼ばれるデータ構造を生成するだけで実行可能なコードが生成できる)。いずれ、セルフホスティングしてコンパイラ自体をExpressoで実装したいところなのですが、パーサーとアナライザの切り離しをどうするか、パーサーはC#のものを使用するとして、現状、パーサーとアナライザは三位一体なので、そうするとあとはコード生成部分程度しかExpressoで書ける部分がなくなり、結局今のままと大して変わらないのではないかなどの問題があり、まだ実現していません。また、Expresso化するにあたって、組み込みのオブジェクト(intseq,slice)の実装をどうするかという問題もあります。intseq型は、ExpressoIntegerSequenceという型をC#で定義しているのですが、C#の機能を利用してEnumeratorを生成しやすくしているので、Expressoに置き換えるなら、それを自分で実装しなくてはならなくなります(コンパイラが自動で行う変換なので、それを知っていれば大した問題ではないかもしれません。yield式を使用するので、状態を保持するステートマシンみたいなものを自分で書かなければならない)。
とまあ、問題はあるものの、コード生成が楽だったり、パーサージェネレータが存在したり、標準でクロスプラットフォームで動くので、C#はオレオレ言語作りに結構向いている環境と言えるかもしれません。まあ、今から言語作りをしたい方にはいきなり言語作りするのではなく、まずはLISPのインタープリタあたりを実装するところから始められることをお勧めしますが。上で出したレキサ、パーサー、インタープリタ、それぞれの機能を具体的にイメージできるようになります。
次に文法についてですが、現状明文化していないので、Cocoのパーサー定義を見ていただくのが一番早いかと思います。中には、パーサー定義を作ったものの、機能の実装をしていなくて動かないものもありますが(具体的にいうとcomprehension, interfaceなどです)。大雑把に文法を把握したいのなら、ExpressoTest/sources配下のファイルが参考になるでしょう。こちらも仮で書いただけの定義があったりして、パースもできないものが含まれていたりしますが、概要を知りたいだけなら十分と思われます。
ドキュメントについては、Rustの公式解説本のようなものを英語でmarkdown形式で書いています。日本語で書き直すのは面倒なので、しないかもしれません。こちらは、Expresso/Documentation/配下に存在します。
まだ、書きたいことはあるような気がしますが、今回の記事はこの程度で、どうしても書いておきたいことができたら、また記事にしようと思います。では( ̄^ ̄)ゞ

2018年2月15日木曜日

オレオレ言語、Expressoについて・・・導入編

こちらでは、ご無沙汰しています、はざまです。
突然ですが、プログラマをされている皆様が生涯で何としても作り上げたいプログラムはなんでしょうか?これは私の願望も多分に含まれているのですが、恐らく一定数の方が、自作言語と答えるのではないでしょうか?

というわけで今回の記事は、自作言語のExpresso(エクスプレッソ)の紹介をします。4,5年前から開発しているオレオレ言語なんですが、最近、try,catchなども実装し、それなりに使える言語になってきたので、α版として公開することにしようかと思った次第です。とは言っても、専用のサイトはまだ用意しませんが。
Expressoという名前は、ExpressiveとEspressoからの造語です。表現力豊かに、かつエスプレッソ(コーヒー)一杯飲む間にでも開発できるような簡潔な言語を目指すという意味を込めています。また、言語のスローガンとして"Easy for beginners, elegant for enthusiasts"という標語も掲げています。「初学者には簡単に、熱狂者には華麗に」という意味ですね。この標語にはあえて、eで始まる単語を多用しています。これは「e(いい)を探す言語」というダジャレです。
Expressoは、オレオレ言語でありながら、普及、実用化させるならPascalのような教育用言語の地位を目指しています。そのために、先ほどの標語のような目標を掲げているわけです。つまり、初学者には簡単に書ける言語、しかしながら、習熟者にとっても、書きやすい言語を目指すということです。

言語仕様としては、まだα版と銘打ってることもあって、かなりガバガバなところが多いんですが、基本は静的型付け、オブジェクト指向を基本とするマルチパラダイム言語になっています。一番強く影響を受けている言語が、Rustなので、Rustで採用されている仕様が結構入っています。
現状、Expressoは、.NET環境上でのみ動く言語になっています。理由は、.NETだと比較的ランタイム環境を整えるのも楽ですし、クロスプラットフォームで動くのが大きいです。まあ、私が、いちばん好きな言語がC#だからというのもありますが……

伝統的なHello worldプログラムの解説をする前に、導入方法を紹介しましょう。現状、専用のサイトがないので、Githubのリポジトリからcloneして導入していただく形になります。こちらからcloneしてください。cloneしたら、git submodule update --initを実行してください。依存リポジトリの解決が行われます(といっても、一つしかありませんが)。そして、cloneしたディレクトリ/ExpressoTest/配下にtest_executablesというディレクトリを作成してください。ここにテストで使用するバイナリが吐かれる設定になっているので、これがないとテストが実行できません。gitにこのディレクトリを追加できるのなら、追加したいところではあります。そうしたら、Mac,Linuxユーザの方なら、あとはメインのソリューションファイルをIDEで開けば、ビルドして実行できるはずです。Windowsユーザの方は、Cocoという依存プログラムを拾ってこなければなりません。あと、パーサ定義をシェルスクリプトで自動生成しているので、その代わりのバッチファイルも書かなきゃダメですね、多分。
Coco/R for C#からCoco.exeを選択してバイナリを拾ってきたら、cloneしたディレクトリ/Expresso/配下に配置してください。Expressoが言語のコアを担うプロジェクトです。バッチファイルは、cloneしたディレクトリ/Expresso/parserCompile.shというシェルスクリプトを参考に作成していただきたいのですが、Coco.exeに渡すオプションは自由に変更してください。バッチファイルを作成したら、Expressoプロジェクト設定でビルド前に自動実行するように設定すれば、いちいち手動でパーサを生成する必要がなくなります。
長々と書きましたが、Windowsでの動作確認は不十分な(動きはするもののテストは通らない程度までしか確認していない)ので、動くことは保証しません。手軽に使いたいなら、Mac+Visual StudioかLinux+Xamarin Studioあたりの環境をお勧めします(昔は、後者、今は前者の開発環境で作ってます)。

さて、ここまでで肝心のコンパイラは動かせるようになったはずなので、伝統的なHello worldプログラムに移ります。Hello worldプログラムは、以下のように書きます。
module main;
def main()
{
    println("Hello, world!");
}

Expressoでは、基本的に1ファイル1モジュール構成を採用しています。ここは、Python譲りですね。各モジュールは、明示的に名前付けすることを義務付けられています。プログラムのエントリーポイントは、mainモジュールのmain関数からになります。今のところ、main関数は、引数も戻り値もなしの仕様になっています(Cのように文字列配列の引数を定義したり、intを戻り値にしても動きますが、単純に無視されます)。
ご覧の通り、関数、メソッドはdefキーワードで定義します。PythonやRubyで採用されている構文だったと思いますが、Rustのようにfunction由来のキーワードだとメソッド定義に違和感があるからです。Rustにはトレイトオブジェクトはあるものの、オブジェクトはないので、メソッドは存在しないはずです。
この例ではどこにも明示されていません(というか変数宣言がない)が、型は後置です。その際、変数名と型の区切り記号には、(-という記号を使用します。これは、数学の∈に由来するExpresso独自の記号(のはず)です。あまり型は明示してほしくないという思想の元、入力しづらい2文字の記号を採用しています。Rustには似た記号を一元化してくれる機能があったと思いますが、Expressoには導入していないので、(-を∈と書いても、認識してくれませんので悪しからず。
関数、メソッドの戻り値を明示する場合は、Rustでも採用されている->記号を使います。関数の戻り値は、return文から推論するので、省略しても構いません(上のmain関数では省略しているが、voidに推論される)。
上記のプログラムで呼び出しているprintln関数は、組み込みの関数です。.NET環境のConsole.WriteLine関数を使用しているので、可変長の引数をとって、それをカンマ区切りで標準出力に出力します。他に、お尻に改行を追加しないprintや、第1引数にConsole.WriteLineに準じるフォーマット文字列を取るprintFormat関数などが存在します。

いかがでしたでしょうか。以上で、オレオレ言語Expressoの導入は終わりです。あ、Expressoの骨格の説明などをしませんでしたが、機能詳細などは次回の記事で行いましょう。コンパイラ作りに興味のある方には、次の記事が参考になるかもしれませんね。

2017年10月30日月曜日

C#のforとforeachに関する思想録

どうも、はざまです。つい先日、とあるニコ生を見ている際に、forとforeachの違いについて言及される場面があり、私がforeachを使うことを勧めたところ、foreachの方がiteratorオブジェクトの破棄がループごとに発生するから遅くなるという指摘をいただき、気になったので速度比較してみることにしました。私はそのとき、え〜Releaseビルドなら、ループ全体でiteratorを使い回すように最適化してくれるんじゃないと答えたのですが、果たして結果は(よく考えたら、iteratorは使いまわさないとおかしいですね。ループ状態を内包しているはずなので)。
今回試したコードは、以下のようなものです。
const int Max = 10_000_000;

var stopwatch = Stopwatch.StartNew();
int result = 0;
foreach(var i in Enumerable.Range(0, Max))
    result += i;

stopwatch.Stop();
Console.WriteLine("foreach Result: {0}/{1}ms", result, stopwatch.ElapsedMilliseconds);

var stopwatch2 = Stopwatch.StartNew();
int result2 = 0;
for(int i = 0; i < Max; ++i)
    result2 += i;

stopwatch2.Stop();
Console.WriteLine("for Result: {0}/{1}ms", result2, stopwatch2.ElapsedMilliseconds);


Stopwatchインスタンスを生成して、foreach/forループ内で足しこむだけです。最後にそれぞれの結果を出力するのは、デッドコード削除で足しこみ自体省略されたら、嫌だなという意図です。このコードだとそれぞれ、確実にオーバーフローが発生しますが、何も書かなければC#はオーバーフローを無視してくれるはずなので、今回は特に考慮していません。さて、この単純なコードで出た結果がこちら。

Debugビルド時
foreach Result: -2014260032/197ms
for Result: -2014260032/41ms

Releaseビルド時
foreach Result: -2014260032/171ms
for Result: -2014260032/26ms

テスト環境はMacBook Pro (Retina, 13-inch, Early 2015)で2.7Ghz Intel Core i5、メモリ16GBです。Debugビルド時でおよそ5倍、Releaseビルド時で7倍弱程度の差ができてしまいました。これを見る限り、ループごとに一時変数を用意してGC走らせていそうなのは間違いなさそうですね。それにしても、結構インパクトでかいです。そっか〜、foreachって遅かったのか〜。とは言っても、可読性重視したいので、使い続けますけどね。for使うとめんどくさいですしね。
速度にうるさいC++のrange-based forとかはどんな実装になってるんだろう。ループ全体で一時変数を使いまわしたりしてないのかな〜。まあ、そうしたら、この一時変数のスコープが大きくなっちゃうんですがね。
では、また(๑╹ω╹๑ )

2017年10月4日水曜日

Rust本の翻訳始めました

お久しぶりです。ご無沙汰してました、はざまです。昔のハンドルネームに戻したりしましたが、ここではこのままはざまと名乗り続けると思います。

さて、本題なのですが、題名の通り、Rustプログラミング言語の公式ドキュメントであるThe book第2版の翻訳を始めました。実際には数ヶ月前から行っているので、もっと早くにご報告していればよかったですね。公式リポジトリで今後大きな変更がないと考えられるFrozen扱いされた章から順に翻訳を進めているので、牛歩ではありますが、お付き合い頂ければと思います。
ドラフトの執筆が完了するまでは、こちらのgithubページのsecond-editionディレクトリで読んでいただくしかないかと思われます。目次がなく読みにくいかとは存じますが、どうぞご容赦ください。

では、今回はここまで。どうぞ、よしなに。

2016年9月12日月曜日

プログラミングにおける演算子と言語の進化に関する思想録

いきなり紛糾から本文を始めてしまいますが、現代において最も幅広く使われている言語の直系の始祖はC言語です。しかし、このC言語は最大にして不可侵の過ちを犯してしまいました。それはそう、"代入演算子"です。
数学における"="記号は"等価"を表します。a = 1という式は、aは1と等しいという意味であり、それ以外の何者でもありません。しかし、あやつ(C言語)はこの記号に代入という新たな意味を割り当て、代わりに等価演算子は"=="という新たな記号を生み出してしまいました。これにより、数学世界とコンピュータ世界の乖離が発生してしまったのです。

この乖離により、コンピュータ言語への入門のハードルが一段階上がってしまったと言えるでしょう。なぜなら、C言語での代入記号の導入により、その血筋を受け継ぐ言語(いわゆるC系言語)においても、"="記号は、代入演算子として使用される羽目となり、その結果、これら後継言語においても数学世界との乖離を生み出してしまったためです。
代入記号の導入により生み出される混乱は以下の通りです。


  1. if文内で数学の"="記号として、この記号を使用する
  2. 実際には代入となるため、左辺の値が変わってしまう
  3. 特にC言語系においては、if文の条件式になんら制約がないので、コンパイルが通ってしまう
  4. 結果、if文内で変数の値が変わってしまい、想定と違う動作をするバグを作りこんでしまう

もちろん、この程度の落とし穴ならば、人間が細心の注意を払えば回避可能ではありますが、現代のコンピュータ業界において、性能の向上は著しいものがあります。このような、多少のコンピュータリソースの消費で回避できうるミスならば、回避できる機構を用意して、使うべきだというのが持論です。
その持論に沿うように、最近作られた言語では、if文の条件式にbool値を返す式しか書けないようになっていたりして対策が施されています。
登場当初は、その万能性から高級言語と呼ばれていたであろうC言語も、今となっては、中級言語と呼ぶべき存在になってしまいました。今後も、言語が進化を続け、ヒューマンエラーの排除を言語が行ってくれるようになるといいですね。

※思想「録」と言いつつ、1エントリーしかありませんσ^_^;

2016年9月9日金曜日

TS LISPソース

どうも、はざまです。
前回の記事で、TS LISPの紹介をしました。今回の記事は、そのTS LISPのソースを全掲載しようと思ったのですが、さすがに長くなりすぎる上に、一覧で見せられても、閲覧性が悪くなるだけなので、その役目はGithubに譲るとして、解説を軽くふわりとするだけに留めようと思います。
中身自体、TypeScriptのコンパイラのバージョンがまだ0.8.*だか、0.9.*の時代に書いたものなので、現在のコンパイラでコンパイルしたら、時代遅れ感が否めませんが、まあ参考になる箇所もあるでしょう。
まあ、解説と言っても、そのファイルが何をしているか概要を説明するだけの簡便なものです。人によっては煩わしく感じるかもしれませんが、お付き合いください。


  • Common.ts - IEnumeratorやDictionaryなど、.NET環境の実行環境で広く必要になる基本的なクラス群を独自定義しています。ただ、このソースを書いた時点のTypeScriptの制限で、ちょっと本物の.NET環境とは異なるメソッド定義になっている箇所があります。新しいTypeScript環境では、解消されているのかな
  • WebHelpers.ts - 見た目をコンソール状にする外部ライブラリ"jqconsole"のラッパと、同じ機能を持つクラスを独自定義しようとしているファイル。ただ、独自実装は、途中で力尽きてます( ;´Д`)
  • ErrorFactory.ts - 様々な種類の例外を投げる"例外ファクトリ"クラスを定義
  • Utils.ts - 全体で必要になる種々のユーティリティ関数群を定義
  • LispTypes.ts - LISPの実行環境となるクラス群を定義
  • Reader.ts - LISPのトークンを識別する文法解析器と"クォーサイクォート"と呼ばれる特殊形式の内部表現を行うクラスを定義
  • LispFunctions.ts - 基本的なLISP関数のネイティブ実装を定義
  • Interpreter.ts - 実際にLISPの処理を行うインタープリタ
  • Snippets.ts - LispFunctions.tsだけでは足りない、よく使われる関数やマクロをLISPとして定義し、文字列で保持するモジュール。load-sample関数で読み込めるS式もこの中に定義されてます
  • main.ts - インタープリタ自体のブートアップを行う処理が記載されている
以上が概要です。こんなほとんど中身のない記事ですが、参考に(?)していただけると光栄です。
今更、なんでこんな記事を書いたのかって怒る方もいらっしゃるかもしれませんね。その理由は、なんとなくとしかお答えできませんε-(´∀`; )

2016年9月7日水曜日

新技術と旧技術の融合

みなさん、ご無沙汰してます。はざまです。
今回は、以前何の目的で作ったかは忘れましたが、作成したLISP処理系の紹介です。といっても、実装内容自体は、完全に借用しているため、そのポーティング程度しか語ることはありません。
そのポーティング先となったのが、今やVisualStudioでも、正式にサポートされ、一線級で活躍していると思われる初期のTypeScriptです。まだ、コンパイラのバージョンが0.8.*だか、0.9.*時代に書いたものなので、今から見ると粗がある可能性も否めませんが、大筋は今の思想と合致しているはずなので、今回、紹介するに至った次第です。これを期に、今後、TypeScriptの記事を増やせていけたらいいな〜とか思ったり、思わなかったり。
Jsdo.itがTypeScriptに対応したとの話も聞くので、修正するなら参考実装もそれに合わせて変更する形になるでしょうかね。
そうそう、Web上で動くLISP実装としての活用もしていただけると、ありがたい限りです。