Grass on SQL

Overview

ちょっと草植えときますね型言語 Grass をSQLで実装しましたという話.

Grassはλ計算をベースにした関数型プログラミング言語です.公式ページの仕様を元にSQL(PostgreSQL)で実装しました.

他Grassについては上記の公式ページとかここら辺を参照.

Example

SELECT run_grass('wwWWwv wwwwWWWwwWwwWWWWWWwwwwWwwv wWWwwwWwwwwWwwwwwwWwwwwwwwww');

 run_grass
-----------
 ww
(1 row)


SELECT run_grass('wWWWwwwwWWWw');

 run_grass
-----------
 x
(1 row)

Homuhomu

ついでに 「ほむほむ」 も入れておきました. 表現が異なるだけで中身はGrassとほぼ一緒.

SELECT
    run_homu($$
        ほむ ほむほむほむ ほむ ほむほむほむほむ ほむ
        ほむ ほむほむ ほむ ほむほむほむ ほむ
        ほむ ほむほむ ほむ ほむほむほむ ほむ
        ほむ ほむほむ ほむ ほむほむほむ ほむ
        ほむ ほむほむ ほむ ほむほむほむ ほむ
        ほむ ほむほむ ほむ ほむほむほむ ほむ
        ほむ ほむほむ ほむ ほむほむほむ ほむ
        ほむ ほむほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむ ほむ ほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ
    $$)
;
   run_homu    
---------------
 Hello, world!
(1 row)

Technique

主に利用したテクニックを紹介

入れ子集合モデル

ASTなどを表現するために木構造が必要になります。

SQLで木構造を扱うために 入れ子集合モデル を採用しています。

CASE

SQLにおけるIFのようなものです

再帰SQL

WITH RECURSIVEという構文を用いて、再帰的にSQLを実行することができます。

前回のSQLで定義された集合にたいして、再度問い合わせをしていくようなイメージです。

型定義

PostgreSQLでは列挙型や構造体のような型をユーザーで定義して用いることができます。

これらは列の別名のようなものなので、必須ではないのですが見やすさのために利用しています。

例)

CREATE TYPE Operation AS ENUM (
    'Abs'
    ,'App'
    ,'Out'
    ,'Succ'
    ,'Char'
    ,'In'
);
CREATE TYPE App AS (
    func Int
    ,arg Int
);

CREATE TYPE Node AS (
    l Int
    ,r Int
    ,op Operation
    ,app App -- for 'App'
    ,ascii Int -- for 'Char'
);

Source Code

全部載せると長いのでgithubのリポジトリを見てみて下さい.

https://github.com/choplin/grass_on_sql

以下簡単に概要を説明.

Functions

run

Grassの実行を行うrun_grass関数ですが,大きく二つのステップに分けて実行しています.

  • parse関数でソースコードからASTへの変換
  • exec関数でASTを受け取って実行
CREATE OR REPLACE FUNCTION run_grass (Text) RETURNS text AS $$
SELECT
    exec( parse($1) )
$$ LANGUAGE SQL
;

parse

parse関数ではASTの構築

メインであるASTの構築はbuild_tree関数で行なっています。

動きはこんな感じです

  • srcでw,Wのまとまり毎に区切って長さを取得
  • recでWITH RECURSIVEを使ってまとまりを順番に消費し、長さをもとにASTを組み立てる
CREATE OR REPLACE FUNCTION build_tree (Text) RETURNS tree AS $$
WITH RECURSIVE
src(chr, len) AS (
    SELECT
        array_agg(substring(s[1] from 1 for 1))
        ,array_agg(char_length(s[1]))
    FROM
        regexp_matches($1, '(w+|W+)', 'g') AS t(s)
)
,rec(tree, idx, nextl) AS (
    SELECT
        tree( ARRAY[]::Node[] )::Tree
        ,1::Int
        ,1::Int
    UNION ALL
    SELECT
        CASE chr[idx]
            WHEN 'w' THEN add_abs_node_n_times(tree, len[idx])
            WHEN 'W' THEN add_node(tree, app_node(nextl,nextl+1,(len[idx],len[idx+1])))
        END
        ,CASE chr[idx]
            WHEN 'w' THEN idx + 1
            WHEN 'W' THEN idx + 2
        END
        ,CASE chr[idx]
            WHEN 'w' THEN nextl + len[idx]
            WHEN 'W' THEN nextl + 2
        END
    FROM
        rec, src
    WHERE
        idx <= array_length(chr, 1)
)
SELECT
    tree
FROM
    rec
ORDER BY
    idx DESC
LIMIT 1
$$ LANGUAGE SQL
;

exec

exec関数では

  • initで初期状態の用意
  • evalでWITH RECURSIVEを使ってASTを辿って実行

の様な動作になっています。

CREATE OR REPLACE FUNCTION exec (Code) RETURNS Text AS $$
WITH RECURSIVE
init(machine) AS (
    SELECT
    machine(
        $1
        ,env( ARRAY[ 4,3,2,1 ] )
        ,dump(
            ARRAY[
                dump_elem(
                    code( ARRAY[ tree( ARRAY[app_node(1,2,(1,1))] ) ] )
                    ,env( ARRAY[ ]::Int[] )
                )
                ,dump_elem(
                    code( ARRAY[ tree( ARRAY[]::Node[] ) ] )
                    ,env( ARRAY[ ]::Int[] )
                )
            ]
        )
        ,closure(
            ARRAY[
                closure_elem(
                    code( ARRAY[ tree( ARRAY[in_node()] ) ] )
                    ,env( ARRAY[ ]::Int[] )
                )
                ,closure_elem(
                    code( ARRAY[ tree( ARRAY[char_node(119)] ) ] )
                    ,env( ARRAY[ ]::Int[] )
                )
                ,closure_elem(
                    code( ARRAY[ tree( ARRAY[succ_node()] ) ] )
                    ,env( ARRAY[ ]::Int[] )
                )
                ,closure_elem(
                    code( ARRAY[ tree( ARRAY[out_node()] ) ] )
                    ,env( ARRAY[ ]::Int[] )
                )
            ]
        )
    )
)
,eval (idx, machine, output) AS (
    (
        WITH sub (tree) AS (
            SELECT
                (machine).code.trees[1]
            FROM
                init
        )
        SELECT
            1::Int AS idx
            ,machine
            ,''::Text
        FROM
            init,sub
    )
    UNION ALL(
        WITH
        prev(idx, machine) AS (
            SELECT
                idx
                ,machine
                ,output
            FROM
                eval
            LIMIT 1
        )
        ,sub(idx, tree, root) AS (
            SELECT
                idx
                ,tree
                ,root(tree)
            FROM(
                SELECT
                    idx
                    ,(machine).code.trees[1] AS tree
                FROM
                    prev
            )t
        )
        SELECT
            idx + 1
            ,CASE
                WHEN isEmpty((machine).code) THEN ret(machine)
                WHEN (sub.root).op = 'Abs' THEN exec_abs(subtrees(sub.tree), machine)
                WHEN (sub.root).op = 'App' THEN exec_app(sub.root, machine)
                WHEN (sub.root).op = 'Out' THEN ret(machine)
                WHEN (sub.root).op = 'Succ' THEN exec_succ(machine)
            END
            ,CASE
                WHEN (sub.root).op = 'Out' THEN output || get_char(machine)
                ELSE output
            END
        FROM
            prev
        INNER JOIN -- 直前以外のprevとJOINされてしまうためINNER JOINを行う
            sub USING(idx)
        WHERE
            NOT (isEmpty((machine).code) AND isEmpty((machine).dump))
    )
)
SELECT
    output
FROM
    eval
WHERE
    output IS NOT NULL
ORDER BY
    idx DESC
LIMIT 1
$$ LANGUAGE SQL IMMUTABLE STRICT
;

Limitation

現状ではGrassの仕様を全ては実装しておらず、サブセットになります。

  • SQLの制限上からINは実装してません
  • FとTはパスしてます

Pull Requestお待ちしてます

後、読みやすさを重視しているので遅いです。誰か最適化して下さい。

Turing Complete

また一つ SQLがチューリング完全である ことが証明されてしまいました。

Wikipediaのチューリング完全のページ にはSQLはチューリング完全でないと堂々と書いてあるのでWikipedianの人は修正をお願いします。

Appendix

余談ですが、こういう “XでYを実装してみた” は早い者勝ちなので、流行り始めた途端に主要な言語は食いつぶされてしまい、僕のような一般人が実装する隙は中々ありません。

そうした中でもSQLは今回のように余りがちなので、悔しい思いをしたことがある方は是非SQLをマスターしてチャレンジしてみて下さい。

Comments