Edited at

そうだ Lisp 作ろう (Make a Lisp in Google BigQuery)

More than 1 year has passed since last update.

こんにちは、トレタ Advent Calendar 2017の8日目記事です。

トレタでは膨大なデータを分析する際に Google BigQuery を使ってますが、今年はデータサイエンティストチームも立ち上がってより大規模な分析が可能になりました。

さてさて、お硬い話は置いといて。クリスマス前なので、ここでは何か面白おかしい SQL 芸でもしましょう。BigQuery は JavaScript ベースのユーザー定義の関数(UDF)をサポートしています、つまり必要であれば好き勝手に UDF で BigQuery の関数を拡張することができます。この際 UDF を使って別の言語を実装して BigQuery のなかで呼び出しましょう!!


イメージ

イメージとしては SQL でこんな呼び出しをできたら面白いと思います。

select mal('(fn* [a b c] (reduce + 0 [a b c]))', [1.1, 2.2, 3.3])

内部はコードを read 関数で AST に変換して、 eval 関数で環境と共に評価します。

Screen Shot 2017-12-07 at 17.17.11.png


Make a Lisp (mal)

今回は Make a Lisp (mal) という Lisp 系の処理系をもとに実装してみようというプロジェクトです。mal の特徴といえば説明が詳しく、多くの言語(現時点で71言語)での実装例を参考にすることが可能です。

内部実装はほとんど mal の JavaScript 実装例を持ってきましたが、Node.js や Browser の環境と異なるため、モジュール周りは変更して、console オヴジェクトがないため prn 系の関数も除外してます。

Screen Shot 2017-12-07 at 17.35.07.png

Source: https://github.com/kanaka/mal/blob/master/process/guide.md#stepA

完成したコードはこちらに公開しております。

https://github.com/hden/mal


UDF 周り

BigQuery の UDF は基本こんな構造になってますが

create temporary function multiplyInputs(x int64, y int64)

returns int64
language js as """
return x * y
""";

OPTIONS セクションを付けることで GCS 上の外部コード、またはライブラリを読み込むことが可能です。例えば自分の gs://hden/mal/ バケット内にコードを置くと

#standardsql

create temporary function mal (str string, args array<float64>)
returns float64
language js as 'return mal.apply(str, args)'
options (
library=['gs://hden/mal/bundle.js']
)
;

こういう風に読み込むことが可能です。


データ型

BigQuery の int 型は基本 int64 ですが、 JavaScript に int64 はないので文字列として渡されます、parseInt() などの関数である程度丸め込むことが可能ですが、注意が必要です。

データ型のマッピングはこちらです。

https://cloud.google.com/bigquery/sql-reference/user-defined-functions?hl=ja#sql-type-encodings-in-javascript


最後に

楽しんでいただけたでしょうか?

#standardsql

create temporary function mal (str string, args array<float64>)
returns float64
language js as 'return mal.apply(str, args)'
options (
library=['gs://hden/mal/bundle.js']
)
;
select mal('(fn* [a b c] (reduce + 0 [a b c]))', [1.1, 2.2, 3.3])

では皆さん、良いクリスマスを。