科学技術計算用プログラミング言語:Sci-Lispの自作(N日目):v0.1.0およびlispmeetupでの発表/今後の方針
Sci-Lisp自作のN日目です。途中で更新が途絶えかけていましたが、Sci-Lisp自体は毎日せっせとUpdateを続けていました。
v0.1.0
作りながら、Rustの勉強をしつつインタプリタの勉強をしていた状態で、現状の設計ではある種の限界を迎えました。 まず第一に、実行速度が圧倒的に遅い。単純にforループを107回回すだけでも3.7秒かかります。(比較のため、Pythonは250msで、10倍以上の差があります。) これは、値の評価や実行時環境からのシンボルの見つけ出しがメモリ・CPU向けに全く最適化されていないからであり、この壁を突破するにはバイトコード式VMを用いたインタプリタにするしかありません。
そして第二に、まったく自己記述的でない(注1)です。100%Rustで書かれています。理想は、コアな部分ないし既存のクレートを最大限活用したい部分だけRustにし、他はSci-Lisp自身で自己記述的に拡張していくべきです。現状の設計では、そのような考慮は全くありません。
第三に、関数型・オブジェクト指向の設計があやふやです。このへんはまだ実装できていないのですが、そもそもこれらのパラダイムに対してどのような設計をすべきかの理解が乏しいです。
今後は、自己記述的で速度の速いマルチパラダイムな(大言壮語)言語に生まれ変わらせるべく、劇的な書き直しをしようと思っています。そのためにまず・・・(3セクション目に続く)
(注1)自己記述的というのは私が勢いで言ったもので厳密な用語ではないようです。なんかそういう雰囲気です。要するに言語をその言語自身で書くということを言いたかったのでした。
Shibuya Lisp lispmeetup #113 での発表
以下のスライドの内容で、発表しました。日ごろからXでお見掛けするつよいLisper/Clojurean達がいらっしゃって、大変刺激を受けました。(私の発表は57分ごろからです)
他の方の発表内容は以下、コメントを添えて
- @t-sin Lispの紹介 - 令和最新版
- @chaploud 自作Lisp処理系:Sci-Lispの紹介
- 割愛
- @masatoi HyREPLの紹介(Hyの開発環境について)
- @lagenorhynque ClojureでのgRPC APIサーバ実装について
- @niyarin Coding-time meta programming
- 機材トラブルにつき発表なし
初めてのShibuya Lisp参戦だったのですが、参加者の方からコメントをいただいたり、一気にGitHubリポジトリのスターが増えたりなど嬉しいことづくめでした。今後もぜひ参加したいです。
今後の方針ーWeb Assembly式VMのスクリプト言語を作る&Juliaの仕様理解
上のスライドで、発表では時間の都合上省いたのですが、そもそもバイトコード式VMを使う際、自分で独自の中間言語を定義するより広く知れ渡った形式を使うべきだよなぁと思い始めていました。 LLVMなどがまさにそれにあたるのですが、どうも規模がでかいうえにドキュメントが読みづらい。そこで、ばったり出会ったのがよく耳にはしていた、Web Assembly(wasm)です。WebとついているからてっきりRust等のコンパイル言語をブラウザで動かせる感じかなー?程度にしか思っていなかったのですが、実際には汎用アセンブリの定義であり、将来的にはあらゆるプラットフォームでそのまま実行できる共通形式(の可能性)であることが分かりました。しかも、wasmのテキスト表現であるwatはなんとS式そのものです。Lispが好きな私にとってはこれとない選択肢で、中間コード(wasm)生成までは自前のパーサー等で行い、その実行は外部クレートに頼ればいいのです。今のところ一番有名なwasmランタイムはwasmerのようです(Rust製)。
そして、wasmを知る副次的な作用として、プログラムは「これだけの命令セット」があれば実現できると知れることです。ちょっと言語化能力が足りずに申し訳ないのですが、スクリプト言語の高速なランタイムを作ろうとしたときに考えるべき事柄がもうすでにwasmには詰まっているということなのです。
さて、もう一つは、Juliaの仕様理解です。私はJuliaを学んだことがなかったのですが、Sci-Lispのターゲットとする科学技術計算をまさにターゲットとしているため、パク真似れるところは真似ていこうと思います。
Shibuya Lisp、オンライン参加もできるので遠方の方も参加できます。とにかく今日は刺激を受けました!また次回!
科学技術計算用プログラミング言語:Sci-Lispの自作(15日目):素晴らしいお手本「ClojureRS」とGitHub workflow
Sci-Lisp自作の15日目です。
ClojureRS
Sci-LispはClojureライクなシンタックスを持つので、字句解析・構文解析・評価などどうしようかなと考えていた時、素晴らしいリポジトリを見つけました。
ずばり、「ClojureRS」です。ClojureをRust上で実装しようという試みのようですね。最終更新日は2023年4月となっています。ライセンスはApache2.0であるため、このコードを参考にしつつSci-Lisp実装をして言ってよさそうです。しばらくは、ClojureRSのコードリーディングに充てることになりそうです。
GitHub workflow
変更をGitHubにpushした際に自動テスト・ビルドが走り、ステータスが見れるといいなと思っていました。cargoで作成したプロジェクトであれば、.github/workflows/rust.yml
に以下のように記述するだけで、フォーマット、テスト、ビルドのチェックをしてくれます。
name: Rust on: push: branches: [ "main" ] pull_request: branches: [ "main" ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Format run: cargo fmt -- --check - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose
また、トップの README.md
に以下のようなリンクを埋め込めば、よく見るステータスバッジが表示されます。
<p align="center"> <a href="https://github.com/chaploud/sci-lisp/actions/workflows/rust.yml"><img src="https://github.com/chaploud/sci-lisp/actions/workflows/rust.yml/badge.svg?branch=main" alt="rust status"/></a> </p>
詳しくは、ワークフロー状態バッジの追加 - GitHub Docs を参考にしてください。
科学技術計算用プログラミング言語:Sci-Lispの自作(14日目):VSCode拡張機能のリリース
Sci-Lisp自作の14日目です。昨日ロゴを作ったこともあり、シンタックスハイライトを行うだけのシンプルなSci-Lisp用拡張機能を作成しました。
VSCodeの拡張機能リリースについては次のページに手順が書いてあります。
https://code.visualstudio.com/api/working-with-extensions/publishing-extension
自分の拡張機能がマーケットプレイスに表示されているのを見ると、とても嬉しいですね。
Sci-Lisp本体の実装に合わせて、VSCode拡張機能も充実させていく予定です。Language Server Protocolに則って動的な文法解析、ハイライト、エラー表示、補完などができたら素晴らしいですね。
科学技術計算用プログラミング言語:Sci-Lispの自作(12・13日目):ひとやすみ(ロゴの作成)
科学技術計算用プログラミング言語:Sci-Lispの自作(11日目):GitHub Copilotの威力
Sci-Lisp自作の11日目です。乗り遅れ気味ですが、GitHub Copilotのライセンスを入手したのでRustによる字句解析を行うコードを書かせてみるなどしました。 正直、「AIが書くコードなんて信用できないわ」と思っていたのですが、Copilot君との対話を続けるうち、「これはやばい」という気持ちになりました。
字句解析・構文解析用クレート「Pest」を書かせてみた
Copilotに次のような指示を出しました。(履歴が消えてしまったので大まかです)
Clojure風のシンタックスを持つ言語の字句解析を行いたいと思います。Rustのクレートであるpestを用いた形式でコードを書いてください。
出来上がったコードがこちらです。
// lexer.pest WHITESPACE = _{ " " | "\t" | "\r" | "\n" | "," } COMMENT = _{ ";" ~ (!"\n" ~ ANY)* ~ "\n" } string = @{ "\"" ~ (!"\"" ~ ANY)* ~ "\"" } regex = @{ "#" ~ "\"" ~ (!"\"" ~ ANY)* ~ "\"" } bool = @{ "true" | "false" } nil = @{ "nil" } integer = @{ ('-' | '+')? ~ ASCII_DIGIT+ } float = @{ ('-' | '+')? ~ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? ~ ("e" ~ ('-' | '+')? ~ ASCII_DIGIT+)? | "nan" | "inf" | "-inf" | "+inf" | "-0." ~ ("0")* } keyword = @{ ":" ~ symbol } symbol = @{ "'" ~ (ASCII_ALPHABETIC | "_") ~ (ASCII_ALPHANUMERIC | "_" | "-" | "*")* } list = @{ "(" ~ expr* ~ ")" } vector = @{ "[" ~ expr* ~ "]" } hashmap = @{ "{" ~ (expr ~ expr)* ~ "}" } hashset = @{ "#{" ~ expr* ~ "}" } quote = @{ "'" ~ expr } backquote = @{ "`" ~ expr } unquote = @{ "~" ~ expr } unquote_splicing = @{ "~@" ~ expr } expr = _{ COMMENT | string | regex | bool | nil | integer | float | keyword | symbol | list | vector | hashmap | hashset | quote | backquote | unquote | unquote_splicing } program = { SOI ~ expr* ~ EOI }
驚くほど正確に私の意図を組んだコードを生成してくれました。Copilotは副操縦士という意味ですが、もはやメインパイロットです。これからはメンターが常にそばに居てくれるような形で開発を進めていけそうです。
科学技術計算用プログラミング言語:Sci-Lispの自作(10日目):方針大転換!Rustで実装することに決めた
Sci-Lisp自作の10日目です。表題の通り、Sci-Lispの実装はC++ではなくRustで行うことに決めました。理由を列挙していきます。
- C++は後方互換性を保ちつつ機能追加されてきたため、仕様が巨大すぎる。
- Rustは後発でC/C++を代替する目的で作成されたため、高速かつ文法が洗練されている。
- Rustにはコードを生成する充実したマクロシステムがある
- C++はUTF-8対応がいまひとつ。Rustは標準で文字列がUTF-8。
- C++は意外とGoogle検索で調べたい内容が出てこない。Rustは勢いがあるためニッチな内容も出てくることが多い
- Rustまわりの開発者体験が非常に良い。(cargo、rust-analyzer、テスト、デバッグ)
- C++で行いたいことはRustでより簡単にできる
まとめると、C++を使うのは開発に苦痛が伴い、Rustを使うのは開発が楽しいということに尽きます。
まだ世に浸透していないのでリポジトリをRustで大きくリプレイスする予定です。
科学技術計算用プログラミング言語:Sci-Lispの自作(9日目):C++によるクラスと出力の定義(基本型)
Sci-Lisp自作の9日目です。C++を勉強しつつSci-Lispの基本型の実装をしました。以下にコード例を載せます。
#include <cmath> #include <iostream> #include <limits> #include <regex> #include <string> #include <iomanip> #define STR "str" #define REGEX "regex" #define BOOL "bool" #define NIL "nil" #define I64 "i64" #define F64 "f64" #define SYM "sym" // base class template <typename T> class Object { public: friend std::ostream& operator<<(std::ostream& os, const Object& v) { return os << v.display(); } friend std::string type(const Object& v) { return v.m_type; } protected: T m_value; std::string m_type; virtual std::string display() const { return "object"; } std::string type() const { return m_type; } }; // str class str : public Object<std::string> { public: str(const char* value) { m_value = std::string(value); m_type = STR; } str& operator=(const str& x) { m_value = x.m_value; return *this; } str& operator=(const std::string& x) { m_value = x; return *this; } private: std::string display() const override { return "\"" + m_value + "\""; } }; // regex class regex : public Object<std::regex> { public: regex(const char* value) { m_string = std::string(value); m_value = std::regex(m_string); m_type = REGEX; } private: std::string m_string; std::string display() const override { return "regex(\"" + m_string + "\")"; } }; // bool // needs call function init() to display bool // nil struct nil_t {}; // TODO: compare class nil : public Object<nil_t> { public: nil() { m_value = {}; m_type = NIL; } private: std::string display() const override { return "nil"; } }; // i64 using i64 = long long; // f64 using f64 = double; // symbol struct sym_t {}; // sym class sym : public Object<sym_t> { public: sym(const char* value) { m_string = std::string(value); m_value = {}; // TODO: compare m_type = SYM; } private: std::string m_string; std::string display() const override { return m_string; } }; template <class T> std::string type(const Object<T>& obj) { return obj.type(); } // for bool std::string type(bool obj) { return BOOL; } // for i64 std::string type(i64 obj) { return I64; } // for f64 std::string type(f64 obj) { return F64; } // nan const f64 NaN = std::numeric_limits<f64>::quiet_NaN(); // inf const f64 inf = std::numeric_limits<f64>::infinity(); // -inf const f64 ninf = -inf; void init(void) { std::cout << std::boolalpha; std::cout << std::scientific << std::setprecision(5); } int main(void) { init(); str s = "hoge"; std::cout << s << " " << type(s) << std::endl; regex re = R"([0-9]+\.\d*)"; std::cout << re << " " << type(re) << std::endl; bool tr = true; bool fa = false; std::cout << tr << " " << fa << " " << type(tr) << std::endl; nil ni; std::cout << ni << " " << type(ni) << std::endl; i64 i = -999; std::cout << i << " " << type(i) << std::endl; f64 f = -3.141592; std::cout << f << " " << type(f) << std::endl; f64 fe = -3.14e15; std::cout << fe << " " << type(fe) << std::endl; f64 na = NaN; f64 posinf = inf; f64 neginf = ninf; std::cout << na << " "; std::cout << posinf << " "; std::cout << neginf << std::endl; sym sy = sym(":hoge"); std::cout << sy << " " << type(sy) << std::endl; return 0; }
この出力は以下のようになりました。
"hoge" str regex("[0-9]+\.\d*") regex true false bool nil nil -999 i64 -3.14159e+00 f64 -3.14000e+15 f64 nan inf -inf :hoge sym
終わりに
次回はコレクションクラスの実装に進めればと思います。