LoginSignup
6

More than 1 year has passed since last update.

スクリプト言語 KINX(ご紹介)

Last updated at Posted at 2020-02-21

はじめに

以前こんなこと(C言語系の構文を持つスクリプト言語が使いたいなー)を書いたのだが、やはり職人として自分の道具は自分で作るか、という誘惑に駆られ作ってみた。

まあ、無い無い言っていても探せばどこかにある(ここでは言わない)ような代物だが、自己満足してても寂しいので、解説の足跡を残しておこう。(本記事を目次に使えるよう追加したら順次更新することにしよう)

目次(個別解説記事へのリンク)

地道に投稿してたら記事数がだいぶ増えた。

何?

手に馴染んでいる伝統的な C 言語系統の Syntax を受け継いだスクリプト言語。目指すは「見た目は JavaScript頭脳(中身)は Ruby安定感は AC/DC(?)」といったところ。

今はまだライブラリや基本メソッドが揃ってないので実用はまだ先だが、言語の基本的な部分は概ね動作する程度にはなった。 バージョン1 をリリースしたし、もう実用できるようになったと言ってしまおう。

見た目は JavaScript

子供ではなく立派な大人。

C 系統で成功しているスクリプト言語と言えば JavaScript。ただし、デスクトップ向けとしてはいまいち。node.js は便利だがヘヴィ過ぎるし、挙動にクセがありすぎて。

一方で、Ruby の思想は好きなのだが、あの end がやたら目につく構文に抵抗がある。普通に通常のキーワードが埋もれるのだが…。

頭脳(中身)は Ruby

そうは言っても Ruby 的思考は嫌いではない。そう、見た目だけの問題なのだ。ならば違う見た目の Ruby になれば良い。

安定感は AC/DC

ブレない所を見習っていこうぜ。

名前の由来

深くは触れられない(?)が、Red Warriors の名盤「KING'S」に遡るとだけ言っておこう。

現時点で「KINX」になったので、由来のほうを(VAN HALEN で有名な「You Really Got Me」のオリジナルである)Kinks に変えるといった「家系図捏造」的なことも視野に入れている。

サンプル

詳しい解説はシリーズ化してお届けしよう。需要があるかは、気にしない。

今すぐ詳しい仕様が知りたいぜ、という方がもしいれば... 「ここ」を参照してください。

fibonacci

fib.kx
function fib(n) {
    if (n < 3) return n;
    return fib(n-2) + fib(n-1);
}

System.println(fib($$[1].toInt()));

まず書くのはベンチマーク。見た目は JavaScript。

Ruby がかつて「スピードが目的じゃないぜ、楽しさなんだぜ?」とみんなを煽っていた懐かしいあの頃。YARV がリリースされてからのノリノリさ加減を見ると、あのスタンスはまさに「酸っぱいブドウ」だったことを目の当たりに…(いや、Ruby 好きなんですよ、見た目以外は)

早速ベンチマークしてみましょう。ついでに Ruby がライバル視している Python にも登場してもらいます。この辺りと比べておけば、だいたいの実力も推測できるというもの。ソースコードは以下の通り。Python3 は Python2 より遅いので 2 で。

fib.rb
def fib(n)
  if n < 3
    return n;
  else
    return fib(n-2) + fib(n-1);
  end
end

puts fib(ARGV[0].to_i)
fib.py
import sys

def fib(n):
    if n < 3:
        return n
    else:
        return fib(n-1) + fib(n-2)

print fib(int(sys.argv[1]))

結果

まず初めにバージョン表示。

$ ruby --version
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86-64-linux-gnu]
$ python --version
Python 2.7.15+

そして結果。単位は「秒」time コマンドで user 時間の速い順。5回くらいやって一番速かったタイム。

言語 fib(34) fib(38) fib(39)
Ruby 0.391 2.016 3.672
Kinx 0.594 4.219 6.859
Python 0.750 5.539 9.109
9227465 63245986 102334155

Ruby 超速いな。遅い遅いはまさしく過去の話だ。むしろ速い部類に入るのではないかと思われるくらい。

Python には勝ったので、まぁこんなもん。スピードキングになろうとは思っていないので、実用的な速度であれば許容範囲としておこう。しかし、実は Kinx には native という必殺技があるのです。正直実用上どこまで役に立つのかはわからないが、可能性を感じて入れてみた。ソースコードは以下。functionnative に変えただけ。

nfib.kx
native fib(n) {
    if (n < 3) return n;
    return fib(n-2) + fib(n-1);
}

System.println(fib($$[1].toInt()));

この軽微な修正がどんな影響を与えるか、先ほどの表に追加して結果を示そう。

言語 fib(34) fib(38) fib(39)
Kinx(native) 0.063 0.453 0.734
Ruby 0.391 2.016 3.672
Kinx 0.594 4.219 6.859
Python 0.750 5.539 9.109
9227465 63245986 102334155

キタ。

既に分かっているとは思うがあえて種明かしをすると、native キーワードが付いた関数はその名の通りマシン語コードにネイティブ・コンパイルし、JIT 実行させている、ということ。そら速いよね。ただし、出力されるアセンブリコードは最適化もレジスタ割当もしておらず全く美しくないが。実用上役に立つかわからない、というのは、色々と制限があるからです。後々触れると思うが、今日は触れない。詳しくは「ここ」を参照。

その他の特徴

シリーズ化するので詳しい解説は今回はしないが、どんなことができるかだけ示しておこう。

プロトタイプベース

JavaScript らしくプロトタイプベース。ただし __proto__ みたいなのは無い。オブジェクト・プロパティに直接メソッドが括りついている。オーバーライドする場合は単に上書きする。class キーワードを用意してあり、クラスの定義ができる。こんな感じ。

class ClassName {

    var privateVar_;

    private initialize() {
        privateVar_ = 0;
        this.publicVar = 0;
    }

    /* private method */
    private method1() { /* ... */ }
    private method2() { /* ... */ }

    /* public method */
    public method3() { /* ... */ }
    public method4() { /* ... */ }

}

var obj = new ClassName();

ガベージコレクション

単純明快な Stop The World のマーク・アンド・スイープ。今のところ困ってない(困るようなほど使ってない)ので、問題無い。問題があったらその時考える。

クロージャ

関数オブジェクトはレキシカル・スコープを持ち、クロージャを実現可能。JavaScript になる(ならないけど)なら当然の動作。こんな感じ。

function newCounter() {
    var i = 0;          // a lexical variable.

    return function() { // an anonymous function.
        ++i;            // a reference to a lexical variable.
        return i;
    };
}

var c1 = newCounter();
System.println(c1()); // 1
System.println(c1()); // 2
System.println(c1()); // 3
System.println(c1()); // 4
System.println(c1()); // 5

ラムダ

無名関数オブジェクトを簡潔に表記できる。ES6 でアロー関数が導入されたが全く同じではなく、先頭に & が必要。なぜかって? Yacc でうまく書けなかったんですよ。コンフリクトが解消できず(すみません)。こんな感じ。

function calc(x, y, func) {
    return func(x, y);
}

System.println("add = " + calc(10, 2, &(a, b) => a + b));
System.println("sub = " + calc(10, 2, &(a, b) => a - b));
System.println("mul = " + calc(10, 2, &(a, b) => a * b));
System.println("div = " + calc(10, 2, &(a, b) => a / b));
// add = 12
// sub = 8
// mul = 20
// div = 5

実はこうも書けるようにしました(追記)。

System.println("add = " + calc(10, 2, { => _1 + _2 }));
System.println("sub = " + calc(10, 2, { => _1 - _2 }));
System.println("mul = " + calc(10, 2, { => _1 * _2 }));
System.println("div = " + calc(10, 2, { => _1 / _2 }));

こうしてもいいです(追記)。何が違うって?コールバックは引数の外側に出せるという感じです。

System.println("add = " + calc(10, 2) { => _1 + _2 });
System.println("sub = " + calc(10, 2) { => _1 - _2 });
System.println("mul = " + calc(10, 2) { => _1 * _2 });
System.println("div = " + calc(10, 2) { => _1 / _2 });

ファイバー

実はこの機能、私は使ったことが無い。ただ Ruby にあるし、便利そうなので実装。こうすればできるかなー、という軽い気持ちで試してみたら割と動いたので。a = yield 10; みたいな。尚、この時の a は呼び出し元の引数の配列が来る。簡単なサンプルはこんな感じ。

var fiber = new Fiber {
    System.println("fiber 1");
    yield;
    System.println("fiber 2");
};

System.println("main 1");
fiber.resume();
System.println("main 2");
fiber.resume();
System.println("main 3");
// main 1
// fiber 1
// main 2
// fiber 2
// main 3

スプレッド演算子

ES6 で導入された(んですよね?)スプレッド(レスト)演算子。そう、これ欲しかったんですよ。超便利。色々使い道はあるが、こんな感じ。

function sample(a1, a2, ...a3) {
    // a1 = 1
    // a2 = 2
    // a3 = [3, 4, 5]
}

sample(1, 2, 3, 4, 5);

最後に

ここまで読んでくださってありがとうございます。まあ、気が向いたら使ってみてください。まだ実用的な使い道は無いと思いますが。

コントリビュートは大歓迎です。一番簡単なコントリビュートは「★」をクリックすることです。まだ全然少ないですが、増えるとモチベーション上がるよね。★が増えるといいな。

ここ (https://github.com/Kray-G/kinx)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
6