comparison paper/type.tex @ 78:897fda8e39c5

Reconstruct paper
author atton <atton@cr.ie.u-ryukyu.ac.jp>
date Wed, 08 Feb 2017 14:49:51 +0900
parents 50c2d2f1a186
children c0199291c58e
comparison
equal deleted inserted replaced
77:50c2d2f1a186 78:897fda8e39c5
1 \chapter{ラムダ計算と型システム} 1 \chapter{ラムダ計算と型システム}
2 \label{chapter:type} 2 \label{chapter:type}
3 \ref{chapter:akasha} 章では CbC のモデル検査的検証アプローチとして、akasha を用いた有限の要素数の挿入時の仕様の検証を行なった。
4 しかし、要素数13個分の挿入を検証しても赤黒木の挿入が必ずバランスするとは断言しづらい。
5
6 そこで、 CbC の性質をより厳密に定義し、その上で証明を行なうことを考えた。
7 CbC のプログラムを証明できる形に変換し、任意の回数の挿入に対しても性質が保証できるよう証明するのである。
8 証明を行なう機構として注目したのが型システムである。
9 3
10 \ref{chapter:type} 章ではCbC の項の形式的な定義の一つとして、部分型を用いて CbC の CodeSegment と DataSegment を定義する。 4 \ref{chapter:type} 章ではCbC の項の形式的な定義の一つとして、部分型を用いて CbC の CodeSegment と DataSegment を定義する。
11 また、型システムの別の利用方法として命題が型で表現できる Curry-Howard 対応を利用した証明が存在するが、その利用方法については\ref{chapter:agda}章で述べる。 5 また、型システムの別の利用方法として命題が型で表現できる Curry-Howard 対応を利用した証明が存在するが、その利用方法については\ref{chapter:agda}章で述べる。
12 6
13 7
14 しかし、さらに多くの要素を検証したり無限回の挿入を検証するには状態の抽象化や CbC 側に記号実行の機構を組み込んだり証明を行なう必要がある。 8 しかし、さらに多くの要素を検証したり無限回の挿入を検証するには状態の抽象化や CbC 側に記号実行の機構を組み込んだり証明を行なう必要がある。
15 CbC は直接自身を証明する機構が存在しない。 9 CbC は直接自身を証明する機構が存在しない。
16 プログラムの性質を証明するには CbC の形式的な定義が必須となる。 10 プログラムの性質を証明するには CbC の形式的な定義が必須となる。
17 11
18 % {{{ 型システムとは
19 \section{型システムとは}
20 型システムとは、計算する値を分類することにでプログラムがある種の振舞いを行なわないことを保証する機構の事である\cite{Pierce:2002:TPL:509043}\cite{pierce2013型システム入門プログラミング言語と型の理論}。
21 ある種の振舞いとはプログラム中の評価不可能な式や、言語として未定義な式などが当て嵌まる。
22 例えば、gcc や clang といったコンパイラは関数定義時に指定された引数の型と呼び出し時の値の型が異なる時に警告を出す。
23 % TODO: C の warning?
24 この警告は関数が受けつける範囲以外の値をプログラマが渡してしまった場合などに有効に働く。
25 加えて、関数を定義する側も受け付ける値の範囲を限定できるため関数内部の処理を記述しやすい。
26
27 型システムで行なえることには以下のようなものが存在する。
28
29 \begin{itemize}
30 \item エラーの検出
31
32 文字列演算を行なう関数に整数を渡してしまったり、データの単位を間違えてしまったり、複雑な場合分けで境界条件を見落すなど、プログラマの不注意が型の不整合となって早期に指摘できる。
33 この指摘できる詳細さは、型システムの表現力とプログラムの内容に依存する。
34 多用なデータ構造を扱うプログラム(コンパイラのような記号処理アプリケーションなど)は数値計算のような数種類の単純な型しか使わないプログラムよりも型検査器から受けられる恩恵が大きい。
35 他にも、ある種のプログラムにとっては型は保守のためのツールともなる。
36 複雑なデータ構造を変更する時、その構造に関連するソースコードを型検査器は明らかにしてくれる。
37
38 \item 抽象化
39
40 型は大規模プログラムの抽象化の単位にもなる。
41 例えば特定のデータ構造に対する処理をモジュール化し、パッケージングすることができる。
42 モジュール化されたデータ構造は厳格に定義されたインターフェースを経由して呼び出すことになる。
43 このインターフェースは利用する側に取っては呼び出しの規約となり、実装する側にとってはモジュールの要約となる。
44
45 \item ドキュメント化
46
47 型はプログラムを理解する際にも有用である。
48 関数やモジュールの型を確認することにより、どのデータを対象としているのかといった情報が手に入る。
49 また、型はコンパイラが実行されるために検査されるため、コメントに埋め込まれた情報と異なり常に正しい情報を提供する。
50
51 \item 言語の安全性
52
53 安全性のの定義は言語によって異なるが、型はデータの抽象化によってある種の安全性を確保できる。
54 例えば、プログラマは配列をソートする関数があった場合、与えられた配列のみがソートされ、他のデータには影響が無いことを期待するだろう。
55 しかし、低水準言語ではメモリを直接扱えるため、予想された処理の範囲を越えてデータを破壊する可能性がある。
56 より安全な言語ではメモリアクセスが抽象化し、データを破壊する可能性をプログラマに提供しないという選択肢がある。
57
58 \item 効率性
59
60 そもそも、科学計算機における最初の型システムは Fortran~\ref{Backus:1978:HFI:960118.808380} などにおける式の区別であった。
61 整数の算術式と実数の算術式を区別し、数値計算の効率化を測るために導入されたのである。
62 型の導入により、コンパイラはプリミティブな演算とは異なる表現を用い、実行コードを生成する時に適切な機械語表現を行なえるようになった。
63 昨今の高性能コンパイラでは最適化とコード生成のフェーズにおいて型検査器が収集する情報を多く利用している。
64
65 \end{itemize}
66
67 型システムの定義には多くの定義が存在する。
68 型の表現能力には単純型や総称型、部分型などが存在し、動的型付けや静的型付けなど、言語によってどの型システムを採用するかは言語の設計に依存する。
69 例えば C言語では数値と文字を二項演算子 \verb/+/ で加算できるが、Haskell では加算することができない。
70 これは Haskell が C言語よりも厳密な型システムを採用しているからである。
71 具体的には Haskell は暗黙的な型変換を許さず、 C 言語は言語仕様として暗黙の型変換を持っている。
72
73 型システムを定義することはプログラミング言語がどのような特徴を持つか決めることにも繋がる。
74
75 % }}}
76 12
77 % {{{ 型無し算術式 13 % {{{ 型無し算術式
78 \section{型無し算術式} 14 \section{型無し算術式}
79 まず、型システムやその性質について述べるためにプログラミング言語そのものの基本的な性質について述べる。 15 まず、型システムやその性質について述べるためにプログラミング言語そのものの基本的な性質について述べる。
80 プログラムの構文と意味論、推論について考えるために自然数とブール値のみで構成される小さな言語を扱いながら考察する。 16 プログラムの構文と意味論、推論について考えるために自然数とブール値のみで構成される小さな言語を扱いながら考察する。
965 閉じた項、という制限が付いているのは $ f \; true $ といった自由変数が存在する項は正規形ではあるが値でないからである。 901 閉じた項、という制限が付いているのは $ f \; true $ といった自由変数が存在する項は正規形ではあるが値でないからである。
966 しかし、開いた項に関しては評価が行なえないために型システムの検査対象に含まれない。 902 しかし、開いた項に関しては評価が行なえないために型システムの検査対象に含まれない。
967 903
968 % }}} 904 % }}}
969 905
970 % {{{ 部分型付け
971
972 \section{部分型付け}
973 単純型付きラムダ計算では、ラムダ計算の項が型付けされることを確認した。
974 ここで、 単純型の拡張として、レコードを導入する。
975
976 レコードとは名前の付いた複数の値を保持するデータである。
977 C 言語における構造体などがレコードに相当する。
978 値を保持する各フィールド $ t_1 $ はあらかじめ定められた集合 L からラベル $ l_i$ を名前として持つ。
979 例えば $ { x : Nat } $ や $ {no : 100, point 33}$ などがこれに相当する。
980 なお、あるレコードの項や値に表れるラベルはすべて相異なるとする。
981 レコードから値を取り出す際にはラベルを指定して値を射影する。
982
983 レコードの拡張の定義は以下である。
984
985 \begin{definition}
986 レコードの拡張に用いる新しい構文形式
987
988 \begin{align*}
989 t :: = ... && \text{項 :} \\
990 \{l_i = t_i^{\; i \in 1 .. n}\} && \text{レコード} \\
991 t.l && \text{射影} \\
992 \end{align*}
993
994 \begin{align*}
995 v :: = ... && \text{値 :} \\
996 {l_i : v_i^{\; i \in 1..n}} && \text{レコードの値}
997 \end{align*}
998
999 \begin{align*}
1000 T :: = ... && \text{型 :} \\
1001 \{l_i : T_i^{\; i \in 1..n}\} && \text{レコードの型}
1002 \end{align*}
1003 \end{definition}
1004
1005 \begin{definition}
1006 レコードの拡張に用いる新しい評価規則
1007
1008 \begin{align*}
1009 \{l_i = v_i^{\; i \in 1..n}.l_j \rightarrow v_j\} && \text{E-PROJRCD} \\
1010 \AxiomC{$t_1 \rightarrow t_1'$}
1011 \UnaryInfC{$t_1.l \rightarrow t_1'.l$}
1012 \DisplayProof && \text{E-PROJ} \\
1013 \AxiomC{$ t_j \rightarrow t_j'$}
1014 \UnaryInfC{$ \{l_i = v_i^{i \in 1..j-1}, l_j = t_j, l_k = t_k^{k \in j+1 .. n}\}
1015 \rightarrow
1016 \{l_i = v_i^{i \in 1..j-1}, l_j = t_j', l_k = t_k^{k \in j+1 .. n}\}
1017 $}
1018 \DisplayProof && \text{E-RCD} \\
1019 \end{align*}
1020 \end{definition}
1021
1022 \begin{definition}
1023 レコードの拡張に用いる新しい型付け規則
1024
1025 \begin{align*}
1026 \AxiomC{各iに対して$ \Gamma \vdash t_i : T_i$}
1027 \UnaryInfC{$ \Gamma \vdash \{ l_i = t_i^{\; i \in 1..n} : \{l_i : T_i^{\; i \in 1..n}\}$}
1028 \DisplayProof && \text{T-RCD} \\
1029 \AxiomC{$ \Gamma \vdash t_1 : \{ l_i : T_i^{\; i \in 1.. n}\}$}
1030 \UnaryInfC{$\Gamma \vdash t_1.lj : T_j$}
1031 \DisplayProof && \text{T-PROJ}
1032 \end{align*}
1033 \end{definition}
1034
1035 レコードを用いることで複数の値を一つの値としてまとめて扱うことができる。
1036 しかし、引数にレコードを取る場合、その型と完全に一致させる必要がある。
1037 例えば $ \{ x : Nat \} $ を引数に取る関数に対して $ \{ x : Nat , y : Bool\}$ といった値を適用することができない。
1038 しかし、直感的には関数の要求はフィールド $x $ が型 $Nat$を持つことのみであり、その部分にのみ注目すると$ \{ x : Nat , y : Bool\}$ も要求に沿っている。
1039
1040 部分型付けの目標は上記のような場合の項を許すように型付けを改良することにある。
1041 つまり型 $ S $ の任意の項が、型 $ T $ の項が期待される文脈において安全に利用できることを示す。
1042 この時、$ S $ を $ T $ の部分型と呼び、 $ S <: T $ と書く。
1043 これは型 $ S $ が型 $ T $よりも情報を多く持っていることを示しており、$S$は$T$の部分型である、と読む。
1044 $ S <: T $ の別の読み方として、型 $ T $ は型 $ S $ の上位型である、という読み方も存在する。
1045
1046 型付け関係と部分型関係をつなぐための新しい型付け規則を考える。
1047
1048 \begin{align*}
1049 \AxiomC{$\Gamma \vdash t : S $}
1050 \AxiomC{$ S <: T $}
1051 \BinaryInfC{$ \Gamma \vdash t : T$}
1052 \DisplayProof && \text {T-SUB}
1053 \end{align*}
1054
1055 この規則は $ S <: T $ ならば $ S $ 型の要素 $ t$はすべて $ T $の要素であると言っている。
1056 例えば、先程の $ \{ x : Nat \} $ と $ \{ x : Nat , y : Bool\}$ が $ \{ x : Nat , y : Bool\} <: \{ x : Nat \}$ となるように部分型関係を定義した時に $ \Gamma \vdash \{x=0, y=1\} : \{ x : Nat \}$ を導ける。
1057
1058 部分型関係は $ S <: T $ という形式の部分型付け式を導入するための推論規則の集まりとして定式化される。
1059 始めに、部分型付けの一般的な規定から考える。
1060 部分型は反射的であり、推移的である。
1061
1062 \begin{align*}
1063 S <: S && \text{S-REFL} \\
1064 \AxiomC{$S <: U$}
1065 \AxiomC{$U <: T$}
1066 \BinaryInfC{$ S <: T $}
1067 \DisplayProof && \text{S-TRANS}
1068 \end{align*}
1069
1070 これらの規則は安全代入に対する直感より正しい。
1071 次に、基本型や関数型、レコード型などの型それぞれに対して、その形の型の要素が期待される部分に対して上位型の要素を利用しても安全である、という規則を導入していく。
1072
1073 レコード型については型 $ T = \{ l_i : T_1 ... l_n : T_n\} $が持つフィールドが型 $ S = \{ k_1 : S_1 ... k_n : T_n\} $のものよりも少なければ $S$ を $T$の部分型とする。
1074 つまりレコードの終端フィールドのいくつかを忘れてしまっても安全である、ということを意味する。
1075 この直感は幅部分型付け規則となる。
1076
1077 \begin{align*}
1078 \{l_i : T_i^{\; i \in 1..n+k} \} <: \{l_i : T_i^{\; i \in 1..n}\}
1079 && \text{S-RCDWIDTH}
1080 \end{align*}
1081
1082 フィールドの多い方が部分型となるのは名前に反するように思える。
1083 しかし、フィールドが多いレコードほど制約が多くなり表すことのできる集合の範囲は小さくなる。
1084 集合の大きさで見ると明かにフィールドの多い方が小さいのである。
1085
1086 幅部分型付け規則が適用可能なのは、共通のフィールドの型が全く同じな場合のみである。
1087 しかし、その場合フィールドの型に部分型を導入できず、フィールドの型の扱いで同じ問題を抱えることとなる。
1088 そのために導入するのが深さ部分型付けである。
1089 二つのレコードの中で対応するフィールドの型が部分型付け関係にある時に個々のフィールドの型が異なることを許す。
1090 これは具体的には以下の規則となる。
1091
1092 \begin{align*}
1093 \AxiomC{各iに対して $S_i <: T_i$}
1094 \UnaryInfC{$\{ l_i : S_i^{\; i \in 1..n}\} <: \{l_i : T_i^{\; i \in 1..n}\}$}
1095 \DisplayProof && \text{S-RCDDEPTH}
1096 \end{align*}
1097
1098 これらを用いて $ \{ x : \{a : Nat , b : Nat\}, y : \{m : Nat\}\}$ が $ \{x : \{ a : Nat\}, y : \{\}\}$の部分型であることは以下のように導出できる。
1099
1100 \begin{prooftree}
1101 \AxiomC{}
1102 \RightLabel{S-RCDWIDTH}
1103 \UnaryInfC{$ \{a : Nat, b : Nat\} <: \{a : Nat\}$}
1104 \AxiomC{}
1105 \RightLabel{S-RCDWIDTH}
1106 \UnaryInfC{$ \{m : Nat\} <: \{\}$}
1107 \RightLabel{S-RCDDEPTH}
1108 \BinaryInfC{$ \{ x : \{a : Nat , b : Nat\}, y : \{m : Nat\}\} <: \{x : \{ a : Nat\}, y : \{\}\}$}
1109 \end{prooftree}
1110
1111 最後に、レコードを利用する際はフィールドさえ揃っていれば順序は異なっても良いという規則を示す。
1112
1113 \begin{align*}
1114 \AxiomC{$ \{ k_j : S_j^{\; i \in 1 .. n} \}$ は $ \{ l_i : T_i^{\; i \in 1 ..n}\} $ の並べ替えである}
1115 \UnaryInfC{$ \{k_j : S_j^{\; j \in 1..n} \} <: \{l_i : T_i^{\; i \in 1..n }\}$}
1116 \DisplayProof && \text{S-RCDPERM}
1117 \end{align*}
1118
1119 S-RCDPERM を用いることで、終端フィールドだけではなく任意の位置のフィールドを削ることができる。
1120
1121 レコードの部分型は定義できたので、次は関数の部分型を定義していく。
1122 関数の部分型は以下 S-ARROW として定義できる。
1123
1124 \begin{align*}
1125 \AxiomC{$ T_1 <: S_1$}
1126 \AxiomC{$ S_2 <: T_2$}
1127 \BinaryInfC{$ S_1 \rightarrow S_2 <: T_1 \rightarrow T_2 $}
1128 \DisplayProof && \text{S-ARROW}
1129 \end{align*}
1130
1131 前提の条件二つを見ると部分型の関係が逆転している。
1132 左側の条件は関数型自身の型と逆になっているため反変と言い、返り値の型は同じ向きであるため共変と言う。
1133 引数について考えた時、求める型よりも大きい型であれば明らかに安全に呼ぶことができるために関数の引数の型の方が上位型になる。
1134 返り値については関数が返す型が部分型であれば上位型を返すことができるため、関数の返り値の方が部分型になる。
1135
1136 別の見方をすると、型 $ S_1 \rightarrow S_2 $ の関数を別の型 $ T_1 \rightarrow T_2 $が期待される文脈で用いることが安全な時とは
1137
1138 \begin{itemize}
1139 \item 関数に渡される引数がその関数にとって想定外でない($ T_1 <: S_1$)
1140 \item 関数が返す値も文脈にとって想定外でない($ S_2 <: T_2 $)
1141 \end{itemize}
1142
1143 という場合に限る。
1144
1145 % }}}
1146
1147 % {{{ 部分型と Continuation based C
1148
1149 \section{部分型と Continuation based C}
1150 部分型を用いて Conituation based C の型システムを定義していく。
1151
1152 まず、DataSegment の型から定義してく。
1153 DataSegment 自体はCの構造体によって定義されているため、レコード型として考えることができる。
1154 例えばリスト~\ref{src:akasha-context}に示していた DataSegment の一つに注目する(リスト~\ref{src:type-ds})。
1155
1156 \lstinputlisting[label=src:type-ds, caption=akashaContext の DataSegment である AkashaInfo] {src/type-ds.h}
1157 この AkashaInfo は $ \{ minHeight : unsigned\; int , maxHeight : unsigned \; int, akashaNode : AkashaNode*\}$ であると見なせる。
1158 CodeSegment は DataSegment を引数に取るため、DataSegment の型は CodeSegment が要求する最低限の制約をまとめたものと言える。
1159
1160 次に Meta DataSegment について考える。
1161 Meta DataSegment はプログラムに出現する DataSegment の共用体であった。
1162 これを DataSegment の構造体に変更する。
1163 こうすることにより、 Meta DataSegment はプログラム中に出現する DataSegment を必ず持つため、Meta DataSegment は任意の DataSegment の部分型となる。
1164 もしくは各 DataSegment の全てのフィールドを含むような1つの構造体でも良い。
1165 第~\ref{chapter:cbc-type}章における Meta DataSegment はそのように定義している。
1166 なお、GearsOSでは DataSegment の共用体をプログラムで必要な数だか持つ実装になっている。
1167
1168 具体的な CbC における Meta DataSegment であるContext (リスト~\ref{src:type-mds})は、 DataSegment の集合を値として持っているために明らかに DataSegment よりも多くの情報を持っている。
1169 \lstinputlisting[label=src:type-mds, caption=CbC の Meta DataSegment である Context] {src/type-mds.h}
1170
1171 部分型として定義するなら以下のような定義となる。
1172
1173 \begin{definition}
1174 Meta DataSegment の定義
1175
1176 \begin{align*}
1177 Meta \; DataSegment <: DataSegment_i^{i \in N} && \text{S-MDS}
1178 \end{align*}
1179 \end{definition}
1180
1181 なお、$N$はあるプログラムに出現するデータセグメントの名前の集合であるとする。
1182
1183 次に CodeSegment の型について考える。
1184 CodeSegment は DataSegment を DataSegment へと移す関数型とする。
1185
1186 \begin{definition}
1187 CodeSegment の定義
1188
1189 \begin{align*}
1190 DataSegment \rightarrow DataSegment && \text{T-CS}
1191 \end{align*}
1192 \end{definition}
1193
1194 そして Meta CodeSegmentは Meta DataSegment を Meta DataSegment へと移す関数となる。
1195
1196 \begin{definition}
1197 Meta CodeSegment の定義
1198
1199 \begin{align*}
1200 Meta \; DataSegment \rightarrow Meta \; DataSegment && \text{T-MCS}
1201 \end{align*}
1202 \end{definition}
1203
1204 ここで具体的なコード(リスト~\ref{src:type-cs})と比較してみる。
1205
1206 \lstinputlisting[label=src:type-cs, caption=具体的なCbCにおける CodeSegment] {src/type-cs.c}
1207
1208 CodeSegment \verb/getMinHeight/ は DataSegment \verb/Allocate/ と \verb/AkashaInfo/ を引数に取っている。
1209 現状は \verb/Context/ も継続のために渡しているが、本来ノーマルレベルからはアクセスできないために隠れているとする。
1210 その場合、引数の型は $ \{ allocate : Allocate , akashaInfo : AkahsaInfo\} $ となる。
1211 また、返り値は構文的には存在していないが、軽量継続で渡す値は $ Context $ である。
1212 よって \verb/getMinHeight/ の型は $ \{ allocate : Allocate , akashaInfo : AkahsaInfo\} \rightarrow Context $ となる。
1213 $ Context $ の型は Meta DataSegment なので、 subtype の S-ARROW より $Context $の上位型への変更ができる。
1214
1215 $ \{ allocat; : Allocate , akashaInfo : AkahsaInfo\} $ を $X$と置いて、\verb/getMinHeignt/ を $ X \rightarrow X $ とする際の導出木は以下である。
1216
1217 \begin{prooftree}
1218 \AxiomC{}
1219 \RightLabel{S-REFL}
1220 \UnaryInfC{$ X <: X $}
1221 \AxiomC{}
1222 \RightLabel{S-MDS}
1223 \UnaryInfC{$ Conttext <: X$}
1224 \RightLabel{S-ARROW}
1225 \BinaryInfC{$ X \rightarrow Context <: X \rightarrow X$}
1226 \end{prooftree}
1227
1228 返り値部分を部分型として定義することにより、軽量継続先が上位型であればどの CodeSegment へと遷移しても良い。
1229 プログラムによっては遷移先は確定しているために部分型にする必要性は無いが、メタ計算やライブラリなどの遷移先が不定の場合は一度部分型として確定し、その後コンパイル時やランタイム時に包摂関係から具体型を導けば良い。
1230 例えばコンパイル時に解決すればライブラリの静的リンク時実行コード生成が行なえ、ランタイム時に解決すればネットワークを経由するプログラムとの接続初期化に利用できる。
1231 例えば、プロトコルがバージョンを持つ場合に接続先のプログラムが利用しているプロトコルと互換性があるかの判断を Context どうしの部分型関係で判断できる。
1232
1233 また、stub のみに注目すると、stub は Context から具体的なDataSegment X を取り出す操作に相当し、S-ARROWの左側の前提のような振舞いをする。
1234 加えて、軽量継続する際に X の計算結果を Context に格納してから goto する部分を別の Meta CodeSegment として分離すると、S-ARROWの右側の前提のような振舞いを行なう。
1235 このようにノーマルレベルの CodeSegment の先頭と末尾にメタ計算を接続することによってノーマルレベルの CodeSegment が型付けできる。
1236 型付けにDataSegment の集合としての Meta DataSegment が必須になるが、これは構造体として定義可能なためコンパイル時に生成することで CbC に組み込むことができる。
1237
1238 なお、メタ計算に利用する Meta DataSegment と Meta DataSegment も同様に型付けできる。
1239 ここで興味深いのはあるレベルの CodeSegment は同レベルの DataSegment において型付けされるが、一つ上の階層から見ると、下の階層のDataSegmentとして一貫して扱えることにある。
1240 このようにメタ計算を階層化することにより、メタ計算で拡張された計算に対しても他のメタ計算が容易に適用できる。
1241
1242 % }}}