butterfly dream

FX自動取引のための時系列分析・信号処理・戦略ブログ

科学技術計算用プログラミング言語:Sci-Lispの自作(N日目):v0.1.0およびlispmeetupでの発表/今後の方針

chaploud-blog.hatenablog.com

Sci-Lisp自作のN日目です。途中で更新が途絶えかけていましたが、Sci-Lisp自体は毎日せっせとUpdateを続けていました。

github.com

v0.1.0

作りながら、Rustの勉強をしつつインタプリタの勉強をしていた状態で、現状の設計ではある種の限界を迎えました。 まず第一に、実行速度が圧倒的に遅い。単純にforループを107回すだけでも3.7秒かかります。(比較のため、Python250msで、10倍以上の差があります。) これは、値の評価や実行時環境からのシンボルの見つけ出しがメモリ・CPU向けに全く最適化されていないからであり、この壁を突破するにはバイトコードVMを用いたインタプリタにするしかありません。

そして第二に、まったく自己記述的でない(注1)です。100%Rustで書かれています。理想は、コアな部分ないし既存のクレートを最大限活用したい部分だけRustにし、他はSci-Lisp自身で自己記述的に拡張していくべきです。現状の設計では、そのような考慮は全くありません。

第三に、関数型・オブジェクト指向の設計があやふやです。このへんはまだ実装できていないのですが、そもそもこれらのパラダイムに対してどのような設計をすべきかの理解が乏しいです。

今後は、自己記述的で速度の速いマルチパラダイムな(大言壮語)言語に生まれ変わらせるべく、劇的な書き直しをしようと思っています。そのためにまず・・・(3セクション目に続く)

(注1)自己記述的というのは私が勢いで言ったもので厳密な用語ではないようです。なんかそういう雰囲気です。要するに言語をその言語自身で書くということを言いたかったのでした。

Shibuya Lisp lispmeetup #113 での発表

以下のスライドの内容で、発表しました。日ごろからXでお見掛けするつよいLisper/Clojurean達がいらっしゃって、大変刺激を受けました。(私の発表は57分ごろからです)

docs.google.com

他の方の発表内容は以下、コメントを添えて

  • @t-sin Lispの紹介 - 令和最新版
  • @chaploud 自作Lisp処理系:Sci-Lispの紹介
    • 割愛
  • @masatoi HyREPLの紹介(Hyの開発環境について)
    • ClojureのnREPLをHyに適用したHyREPLをメンテされている、いずれは私もSci-LispのLanguage Serverを作るつもりなので、大変参考になった
  • @lagenorhynque ClojureでのgRPC APIサーバ実装について
    • ClojureとgRPCをガシガシと使ったことがなかったので深くは理解できなかったが、Clojureの情報リソースが少ないなかPythonから処理をまねていると聞き、やはり開拓者の営みはそうだよなぁと感じた
  • @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製)。

github.com

そして、wasmを知る副次的な作用として、プログラムは「これだけの命令セット」があれば実現できると知れることです。ちょっと言語化能力が足りずに申し訳ないのですが、スクリプト言語の高速なランタイムを作ろうとしたときに考えるべき事柄がもうすでにwasmには詰まっているということなのです。

さて、もう一つは、Juliaの仕様理解です。私はJuliaを学んだことがなかったのですが、Sci-Lispのターゲットとする科学技術計算をまさにターゲットとしているため、パク真似れるところは真似ていこうと思います。

Shibuya Lisp、オンライン参加もできるので遠方の方も参加できます。とにかく今日は刺激を受けました!また次回!

科学技術計算用プログラミング言語:Sci-Lispの自作(15日目):素晴らしいお手本「ClojureRS」とGitHub workflow

chaploud-blog.hatenablog.com

Sci-Lisp自作の15日目です。

ClojureRS

Sci-LispClojureライクなシンタックスを持つので、字句解析・構文解析・評価などどうしようかなと考えていた時、素晴らしいリポジトリを見つけました。

github.com

ずばり、「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拡張機能のリリース

chaploud-blog.hatenablog.com

Sci-Lisp自作の14日目です。昨日ロゴを作ったこともあり、シンタックスハイライトを行うだけのシンプルなSci-Lisp拡張機能を作成しました。

VSCode拡張機能リリースについては次のページに手順が書いてあります。

https://code.visualstudio.com/api/working-with-extensions/publishing-extension

自分の拡張機能マーケットプレイスに表示されているのを見ると、とても嬉しいですね。

VSCode MarketplaceでのSci-Lispの表示

拡張機能GitHubリポジトリは以下です。

github.com

Sci-Lisp本体の実装に合わせて、VSCode拡張機能も充実させていく予定です。Language Server Protocolに則って動的な文法解析、ハイライト、エラー表示、補完などができたら素晴らしいですね。

科学技術計算用プログラミング言語:Sci-Lispの自作(12・13日目):ひとやすみ(ロゴの作成)

chaploud-blog.hatenablog.com

Sci-Lisp自作の12・13日目です。本業が忙しくてなかなか進めません。現在はRustによるLisp実装のリポジトリを読解している最中です。 名だたるOSSには必ずロゴがあるので、やる気を出すためにロゴを作成してみました。

Sci-Lispのロゴ

GitHubのSci-LispリポジトリのREADME.mdにも反映させました。なんだか嬉しくなってきますね!

github.com

科学技術計算用プログラミング言語:Sci-Lispの自作(11日目):GitHub Copilotの威力

chaploud-blog.hatenablog.com

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 }

pest.rs

驚くほど正確に私の意図を組んだコードを生成してくれました。Copilotは副操縦士という意味ですが、もはやメインパイロットです。これからはメンターが常にそばに居てくれるような形で開発を進めていけそうです。

科学技術計算用プログラミング言語:Sci-Lispの自作(10日目):方針大転換!Rustで実装することに決めた

chaploud-blog.hatenablog.com

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++によるクラスと出力の定義(基本型)

chaploud-blog.hatenablog.com

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

終わりに

次回はコレクションクラスの実装に進めればと思います。