Get Slide

1
ユーザ定義演算子による
内部DSLの構成法
市川 和央 千葉 滋
東京工業大学大学院
2
Domain Specific Language (DSL)
• 用途に応じたミニ言語
SQL
select name from register where age < 30
Make
hello : hello.c
cc –c hello.c
3
内部DSL
• 一つの汎用的な言語の中でDSLを実現
SQL
ResultSet rs =
select name from register where age < 30;
Make
MakeRule hello = “hello.c”
cc –c “hello.c”;
タブ
4
比較
if ( 0 <= a < 10 ) {
...
}
Map
HashMap<String, Integer> modifiers = {
“public” -> 1
“private” -> 2
“protected” -> 4
...
};
5
従来の手法
• (ホスト言語に組み込む)
• ソースコード変換
• マクロ
• 柔軟な記法を用意
• シンタックスシュガーを利用
6
柔軟な記法を用意
☓ DSLの構文がホスト言語に制限される
◯ 複数のDSLやホスト言語と同時に利用できる
例: Scala
val rs =
sql select “name” from register where (“age” < 30)
if (a < 30) { ... }
等価
構文の制限
val rs =
sql.select(”name”).from(register).where(“age”.<(30))
他のDSLやホスト言語を壊さない
if(a.<(30)) { ... }
7
ソースコード変換
◯ DSLの構文がホスト言語に殆ど制限されない
☓ 複数のDSLやホスト言語と同時に利用できない
ResultSet rs =
select name from register where age < 30;
if (a < 30) { ... }
変換
自由な構文
ResultSet rs =
sql_select(name, register, sql_lt(age, 30));
if (sql_lt(a, 30)) { ... }
もとの意味を破壊
8
提案:
ユーザ定義演算子による
内部DSLの構成
9
ユーザ定義演算子の利用
• DSLの構文をN項演算子として表現
• N項演算子の形式は自由
ResultSet rs =
select name from register where age < 30;
三項演算子
select from where
二項演算子
<
10
期待される型により演算子を制限
• 返り値の型が期待される型である場合のみ有効に
• オペランドも再帰的に制限される
ResultSet rs =
select name from register where age < 30;
11
期待される型により演算子を制限
• 返り値の型が期待される型である場合のみ有効に
• オペランドも再帰的に制限される
ResultSet rs =
select name from register where age < 30;
ResultSet型が期待されるので、
select...from...where...が有効に
12
期待される型により演算子を制限
• 返り値の型が期待される型である場合のみ有効に
• オペランドも再帰的に制限される
ResultSet rs =
select name from register where age < 30;
ResultSet型が期待されるので、
select...from...where...が有効に
条件節が期待されるので、
専用の<演算子が有効に
14
従来の手法の問題点を解決
◯ DSLの構文がホスト言語に殆ど制限されない
 演算子によって文法が切り替わる
◯ 複数のDSLを組み合わせて利用できる
 期待される型により有効な演算子が制限される
15
制限
• 型推論との相性が最悪
• クラス定義等の式より大きいものは表現できない
• 左辺値等の期待される型がない所では利用できない
• 動的型付け言語では不可能
16
LasticJ
• Javaのサブセットに前述のアイデアを導入
• ジェネリクス・インターフェース等はなし
• コンパイラはJavaにより実装
• 名前は変わるかも
17
Select文の作り方
18
三項演算子 select from where の定義
• メソッド定義と似た形式
• 処理内容はホスト言語で記述
返り値の型
キーワード
オペランド
ResultSet “select” col “from” table “where” cond
(readas SQLColumn col, SQLTable table, SQLCond cond)
: priority = 200
{
オペランドの型
Connection conn = ...;
Statement stmt = conn.prepareStatement(...);
return stmt.executeQuery();
}
処理内容
19
演算子モジュール
• 同一の機能に関する演算子を集めたもの
• 演算子定義はここに記述
三項演算子
select from where
operators SQLOperators {
ResultSet “select” col “from” table “where” cond
(readas SQLColumn col, SQLTable table, SQLCond cond)
: priority = 200 { ... }
SQLCond col “<“ val (readas SQLColumn col, int val)
: priority = 100 { ... }
...
}
二項演算子
<
20
演算子の利用
• using節により利用する演算子モジュールを指定
using lasticj.test.sql.SQLOperators;
class Test {
SQLOperatorsを利用
public void run() {
SQLTable register = ...;
ResultSet rs =
select name from register where age < 30;
...
}
SQLOperatorsの演算子を利用して解釈
}
21
readas修飾子
• オペランドの位置の単語をリテラルとして読む
ResultSet “select” col “from” ...
(String col, ...) ...
このようなダブルクォー
トは書きたくない
ResultSet rs =
select “name” from register where “age” < 30;
ResultSet “select” col “from” ...
(readas SQLColumn col, ...) ...
ResultSet rs =
SQLColumn型の
リテラルとして解釈
select name from register where age < 30;
24
本体部分の構文解析法
• Packrat parsingのアルゴリズムを利用
• 字句解析器・構文解析器・型チェッカーが一体化
• 式の解析は次のようにして行う
1.
期待される型を返す演算子を優先度順に試す
2.
最初に成功したものを結果として返す
3.
全て失敗した場合、通常のJavaのルールで解析する
25
関連研究
• SugarJ [ S. Erdweg ら ’11 ]
• ユーザがシンタックスシュガーをライブラリとして定
義
• 文法が衝突すると一方が破壊される
• Template Haskell [ T. Sheard ら ‘02 ]
• 型安全なコンパイル時メタプログラミング
• ユーザが直接構文木を操作しなければならない
• Scala
• メソッド呼び出しを単項・二項演算のように記述できる
• 実現可能な内部DSLの文法に制約がある
26
まとめ
• ユーザ定義演算子による内部DSLの構成法を提案
• このアイデアを導入した言語LasticJを作成
今後の課題
• 記述力の強化
• 様々な内部DSLを実際に構築する
27
ResultSet rs =
select name from register where age < 30;
operators SQLOperators {
ResultSet “select” col “from” table “where” cond
(readas SQLColumn col, SQLTable table, SQLCond cond)
: priority = 200 { ... }
SQLCond col “<“ val
(readas SQLColumn col, int val)
: priority = 100 { ... }
}
28
MakeRule hello = “hello.c”
cc –c “hello.c”;
operators MakeOperators {
MakeRule file “\t“ script
(String file, Script script)
: priority = 100 { ... }
Script command option file
(readas Command command, Option option, String file)
: priority = 100 { ... }
Option “-” op (readas String op)
: priority = 100 { ... }
}
29
if ( 0 <= a < 10 ) {
...
}
operators BoolOperators {
boolean a “<=“ b “<“ c (int a, int b, int c)
: priority = 1000 { ... }
}
30
HashMap<String, Integer> modifiers = {
“public” -> 1
“private” -> 2
“protected” -> 4
...
}; operators MapOperators {
Map<K, V> “{“ entries “}” (MapEntry<K, V>... entries)
: priority = 100 { ... }
MapEntry<K, V> key “->” val (K key, V val)
: priority = 100 { ... }
}
注) 現在の実装ではジェネリクス及び0回以上の繰り返しを示す...には対応
していないため、実際には余分なクラスと演算子をいくつか作る必要がある。