スライド

Download Report

Transcript スライド

OCommand : 型安全な
シェルプログラミングのための
領域特化言語の提案
朝倉 泉
増原 英彦
青谷 知幸
東京工業大学 数理・計算科学専攻
1
OCommand
• OCaml上で安全にシェルコマンドを
呼び出すための処理系
– コマンドの仕様を書くためのDSL
– コマンドの結果にアクセスする書式を変換す
る処理系
• コマンド出力へのアクセスに対する
安全性を保証
2
lsコマンドを利用するプログラム例
• ls -lの出力からサイズの合計が与えられた閾
値より小さいファイル集合を求めたい
• 例 閾値=10000Byte
ファイルサイズ
$ ls –l
-rw-r----1 root admin 2682 Dec
-rw-r----1 root admin 2459 Jan
-rw-r----1 root admin 2397 Jan
-rw-r--r-1 root wheel
607 Jan
-rw-r----1 root admin 12217 Feb
-rw-r----1 root
admin
12986 Feb
{perm:perm; hard:int;
...
;size:int; ...
ファイル名
17 20:37 a
10 14:54 b
10 14:55 c
16 07:53 d
4 17:45 e
5;name:string}
00:02 f
各欄が適切なデータ型に変換されたレコード型として扱いたい
3
lsコマンドを利用するプログラム例
• コマンドはオプション集合を引数に取る関数(コマンド
関数)
• コマンド関数の戻り値はレコード(出力行レコード)
のリスト
let collect_files size =
let files = Ls.command (add_l empty) in
let rec iter cur_size acc files =
match files with
指定されているオプションの集合
| [] -> acc
(オプション集合)
| file :: files ->
let cur_size = cur_size + file..size in
if cur_size > size then acc
else iter cur_size (file..name :: acc) files
in
iter 0 [] files
;;
4
目標1/5. コマンドの関数化
let collect_files size =
1. コマンドをオプションを引
数に取る関数として扱う
let files = Ls.command (add_l empty) in
let rec iter cur_size acc files =
emptyは空のオプション集合
match files with
| [] -> acc
add_lはオプション集合に
lオプションを追加する関数
| file :: files ->
let cur_size = cur_size + file..size in
if cur_size > size then acc
else iter cur_size (file..name :: acc) files
in
iter 0 [] files
;;
5
目標2/5. 出力のレコード化
let collect_files size =
let files = Ls.command (add_l
let rec iter cur_size acc files =
match files with
| [] -> acc
| file :: files ->
empty)
in
let cur_size = cur_size + file..size in
if cur_size > size then acc
else iter cur_size (file..name :: acc) files
in
iter 0 [] files
;;
2. コマンド出力が自動的に
レコードに変換される
6
目標3/5. レコード型の柔軟な変化
let collect_files size =
let files = Ls.command (add_i (add_l empty)) in
let rec iter cur_size acc files =
iオプションを追加して
match files with
| [] -> acc
出力行レコードにinodeという
| file :: files ->
int型のフィールドを追加
3.オプションが変化する
let cur_size = cur_size + file..size in
と出力レコードの型も
if cur_size > size then acc
適切に変化する
else iter cur_size (file..name :: acc) files in
iter 0 [] files
;;
7
目標4/5. フィールドアクセスの検査
4.ユーザがアクセスしたフィール
let collect_files size = ドが存在することが検査できる
let files = Ls.command (add_i
let rec iter cur_size acc files =
$
ls files
–i with
match
| [] -> a
acc
10358
| file :: files ->
empty)
in
305049
b
let cur_size = cur_size + file..size in
301392
c
if cur_size
> size then acc
940030
elsediter cur_size (file..name :: acc) files
in61038
e
iter 0 [] files
573013
f
出力にsizeフィールドが無いので
;;
コンパイルエラー
8
目標5/5. オプションの合成
opt size =
Ls.command (opt (add_l empty))
let collect_files
let files =
in
let rec iter cur_size acc files =
match files with
5.オプションを合成可能
| [] -> acc
にすること
| file :: files ->
let cur_size = cur_size + file..size in
if cur_size > size then acc
else iter cur_size (file..name :: acc) file
in
iter 0 [] files
;;
collect_files add_t size
collect_files add_S size
更新日時順
サイズ順
9
目標を実現する方針
• 1.コマンドの関数化
• 2.出力のレコード化
•
DSLによるコマンドの
仕様の記述
•
オプション集合をsingleton type
で表す
出力行レコードに型レベル関数を
用いる
•
フィールドアクセスが安
全であることの証明を
生成
•
オプション集合を
タプルで表現
• 4.レコード型の柔軟な変化
• 3.フィールドの検査
• 5.オプションの合成
10
目標を実現する方針
• 1.コマンドの関数化
• 2.出力のレコード化
•
DSLによるコマンドの
仕様の記述
•
オプション集合をsingleton type
で表す
出力行レコードに型レベル関数を
用いる
•
フィールドアクセスが安
全であることの証明を
生成
•
オプション集合を
タプルで表現
• 4.レコード型の柔軟な変化
• 3.フィールドの検査
• 5.オプションの合成
11
提案1/2:OCommand DSL/
関数と型定義の導出
• コマンドの形式を記述する領域特化言語
(DSL)
– オプションをコマンドに渡した時に,どのよう
に形式が変化するかを記述
• コマンドの記述からコマンドをOCaml上で
型安全に扱うための関数,型定義を導出
12
DSLによる形式の定義
コマンドの名前
フィールドの出現順序
COMMAND ls : BEGIN
ORDER {inode; owner; group; ...; name}
OPTION i : ADD {inode : int}
OPTION l : ADD {perm : perm; hard : int; ...}
OPTION h : MODIFY {size : hsize}
DEFAULT : {name : string}
END
オプションの作用
• オプションの作用:オプションが指定された時に出力
の形式がどう変化するか
13
DSLの生成コード
• DSLで記述されたコマンドの仕様からコマ
ンド関数の定義などが生成される
module Ls =
...
let empty
let add_i
let add_l
let add_h
struct
=
opt =
opt =
opt =
...
...
...
...
let command opts = ...
end
オプション集合を操作
する関数の定義
コマンド関数
14
目標を実現する方針
• 1.コマンドの関数化
• 2.出力のレコード化
•
DSLによるコマンドの仕
様の記述
•
オプション集合をsingleton type
で表す
出力行レコードに型レベル関数を
用いる
•
フィールドアクセスが安
全であることの証明を
生成
•
オプション型をタプルで
表現
• 4.レコード型の柔軟な変化
• 3.フィールドの検査
• 5.オプションの合成
15
適切な型検査
• コマンド関数は引数のオプション集合に
よって出力行レコード型が異なる
# Ls.command empty
- : {name : string} list
引数の値毎に異なる
レコード型を返す
# Ls.command (add_i empty)
- : {inode : int; name : string} list
# Ls.command (add_i (add_l empty))
- : {inode : int; perm : …; name : string} list
16
提案2/2:型レベル関数を用いた
コマンド関数のエンコード
• オプション集合をGADTsによって
singleton typeにする
• 出力行レコードに型レベル関数を用いる
コマンド関数
:
オプション集合型
一対一対応
(singleton type)
→
出力行レコード型
型レベル関数
オプション集合値
依存
17
提案2/2:型レベル関数を用いた
コマンド関数のエンコード
• オプション集合をGADTsによって
singleton typeにする
• 出力行レコードに型レベル関数を用いる
command
:
('i opt_i *
'l opt_l *
'h opt_h)
→
(('i, 'l, 'h) perm * ... *
('i, 'l, 'h) size * ... *
('i, 'l, 'h) name))
list
• レコードをタプルで表現
• perm, size, name等が型レベル関数
18
オプション指定型
• オプションが指定されているかどうかを
表す型
– GADTsを用いて定義
(* on, offは抽象型 *)
type 'i opt_i = I_On : on opt_i
| I_Off : off opt_i
let
let
let
let
empty
add_i
add_l
add_h
= (I_Off,
(_, l, h)
(i, _, h)
(i, l, _)
L_Off, H_Off)
= (I_On, l,
h)
= (i,
L_On, h)
= (i,
l,
H_On)
19
提案2/2:型レベル関数を用いた
コマンド関数のエンコード
• オプション集合をGADTsによって
singleton typeにする
• 出力行レコードに型レベル関数を用いる
command
:
('i opt_i *
'l opt_l *
'h opt_h)
→
(('i, 'l, 'h) perm * ... *
('i, 'l, 'h) size * ... *
('i, 'l, 'h) name))
list
• レコードをタプルで表現
– perm, size, name等が型レベル関数
20
フィールド型
• 各オプション指定型からフィールド出現
型への型レベル関数
– フィールド出現型:フィールドが存在して
その型がτであることを表す型τ preと
フィールドが存在しないことを表すabs
'i
'l
'h
size (off, on, off) = int pre
size (off, on, on) = string pre
size (off, off, off) = abs
• OCamlは型レベル関数を直接サポートしていな
いので何らかのエンコードが必要
21
フィールド型
type ('i, 'l, 'h) size =
| Size : 'fld field * ('i, 'l, 'h, 'fld) rel_size
型引数'fldは存在量化されて外から隠されている
'fld fieldは'fldがpreの時のみ値を持つ型(GADTs)
type 'fld field =
| FPre : 'a -> 'a pre field
| FAbs : abs field
22
フィールド型
type ('i, 'l, 'h) size =
| Size : 'fld field * ('i, 'l, 'h, 'fld) rel_size
• ∃x:(i, l, h, fld)rel_size ⇔ size(i,l,h)=fld
type ('i, 'l, 'h, 'fld) rel_size =
| Rel_size_0 : (off, off, off, abs)
| Rel_size_1 : (off, off, on, abs)
| Rel_size_2 : (off, on, off, int pre)
| Rel_size_3 : (off, on, on, string pre)
(* other 4 combinations of options *)
rel_size
rel_size
23
rel_size
rel_size
フィールド型
type ('i, 'l, 'h) size =
| Size : 'fld field * ('i, 'l, 'h, 'fld) rel_size
• x0
x0
• x1
x1
:
=
:
=
(off, off, off) size ⇒
(FAbs
* Rel_size0)
(off, on, off) size ⇒
(FPre (int型の値) * Rel_size2
型引数毎に'fldの型が変化している
24
目標を実現する方針
• 1.コマンドの関数化
• 2.出力のレコード化
•
DSLによるコマンドの仕
様の記述
•
オプション集合をsingleton type
で表す
出力行レコードに型レベル関数を
用いる
•
フィールドアクセスが安
全であることの証明を生
成
•
オプション集合を
タプルで表現
• 4.レコード型の柔軟な変化
• 3.フィールドの検査
• 5.オプションの合成
25
フィールドアクセス≠パターンマッチ
• 型を無視するとパターンマッチで取り出
せば良い
file..size
match file with
| (..., Size (Fpre res, _), ...) -> res
存在量化されたresの型がエスケープするので
型エラー
26
フィールドアクセス=パターンマッチ+証明
• 存在型が具体的なフィールド出現型に等
しいことを証明して取り出す
file..size
(off, on, off, ?) rel_size ->
(?, int pre) eq
match file with
| (..., Size (x, rel), ...) ->
let eq = eq_rel_size_off_on_off rel in
let FPre res = apply_eq eq x in
res
(?, int pre) eq -> ? field -> int pre field
27
実装
DSLで記述された
コマンドの形式
コマンドを使う
スクリプト
ダミープログラム
の生成(100行)
コマンドモジュール
の生成(600行)
フィールド出現型
を証明する関数
型/関数定義
合計1000行程度の
システムになった
スクリプトの変換
(200行)
証明付きの
スクリプト
型情報を得るための
ダミーコード
ocaml
コンパイラ
型情報
(.annot)
28
事例研究
• lsコマンドの他に,df,psコマンドを本研
究が提案するDSLで扱えることを確かめた
COMMAND df : BEGIN
ORDER : {filesystem; type; size; used;
available; use; mounted_on} ;;
OPTION T : ADD {type : string};;
OPTION h : MODIFY {size : hint; use : hint;
avail : hint} ;;
DEFAULT : {filesystem : string; size : int;
used : int; available : int;
use : int; mouted_on : string}
END;;
フィールド数:7
対応したオプション:T,h
COMMAND ps : BEGIN
ORDER : {f; s; uid; pid; ppid; c; pri;
ni; addr; sz; wchan; stime;
tty; time; cmd;} ;;
OPTION e : ADD {}
OPTION f : ADD {uid : string; ppid : int;
c : int; stime : time} ;;
OPTION l : ADD {f : int; s : string; uid : int;
ppid : int; c : int; pri : int;
ni : int; addr : string;
sz : int; wchan : string}
DEFAULT : {uid : int; tty : string; time : string;
cmd : string with delim = ""}
END;;
フィールド数:15
対応したオプション:e,f,l
29
関連研究 : Caml-Shcaml
[Heller, A. and Tov, J. A., ML2008]
• 手法
– コマンドの形式をモジュールで記述
– モジュールをファンクターに渡すことでコマン
ド関数等を生成
• 本研究との相違点
– オプションの合成等は扱っていない
– 出力行レコードへのアクセスに対する型安全性
はない
30
課題: 多相なオプションへの対応
let sum_size
opt size =
= Ls.command (opt (add_l empty))
let files
List.fold files ~init:0 ~f:(fun acc file ->
acc +
in
file..size)
;;
• この時, optには
“lオプションと組み合わせた時の出力行レコードには
sizeというint型のフィールドが存在する”ような任意の型
という型が付いてほしい
• OCommandエンコードでは表現できない
• 引数のオプションに対する制約はHaskellの
type classと関数従属性を用いて表現できる
31
結論
• コマンドの仕様を記述し、コマンドを
OCaml上で呼び出すためのコードを
生成するためのDSLを提案
– オプションを指定した時にどのように出力を
変化させるかを記述
• コンパイル時にフィールドアクセスが安全であ
ることを検査するためのOCaml上での型の表現
を与えた
– GADTsを用いて表現
• ls,ps,dfが扱えることを確かめた
• 多相なオプションをどう扱うかは今後の課題
32