# HG changeset patch # User atton # Date 1486612582 -32400 # Node ID c0199291c58ee7add953b22ab5dd87a6297371e5 # Parent 39a27b704f0c67eb4a5bd03c19f929553210be75 Add simple/sub type description diff -r 39a27b704f0c -r c0199291c58e paper/agda.tex --- a/paper/agda.tex Wed Feb 08 17:39:12 2017 +0900 +++ b/paper/agda.tex Thu Feb 09 12:56:22 2017 +0900 @@ -1,62 +1,10 @@ \chapter{証明支援系言語 Agda による証明手法} \label{chapter:agda} -\ref{chapter:akasha} 章では CbC のモデル検査的検証アプローチとして、akasha を用いた有限の要素数の挿入時の仕様の検証を行なった。 -しかし、要素数13個分の挿入を検証しても赤黒木の挿入が必ずバランスするとは断言しづらい。 - -そこで、 CbC の性質をより厳密に定義し、その上で証明を行なうことを考えた。 -CbC のプログラムを証明できる形に変換し、任意の回数の挿入に対しても性質が保証できるよう証明するのである。 -証明を行なう機構として注目したのが型システムである。 +\ref{chapter:type}章ではCbCにおける CodeSegment と DataSegment が部分型で定義できることを示した。 型システムは Curry-Howard 同型対応により命題と型付きラムダ計算が一対一に対応する。 依存型という型を持つ証明支援系言語 Agda を用いて型システムで証明が行なえることを示す。 -% {{{ 型システムとは -\section{型システムとは} -型システムとは、計算する値を分類することにでプログラムがある種の振舞いを行なわないことを保証する機構の事である\cite{Pierce:2002:TPL:509043}\cite{pierce2013型システム入門プログラミング言語と型の理論}。 -ある種の振舞いとはプログラム中の評価不可能な式や、言語として未定義な式などが当て嵌まる。 -例えば、gcc や clang といったコンパイラは関数定義時に指定された引数の型と呼び出し時の値の型が異なる時に警告を出す。 -この警告は関数が受けつける範囲以外の値をプログラマが渡してしまった場合などに有効に働く。 -加えて、関数を定義する側も受け付ける値の範囲を限定できるため関数内部の処理を記述しやすい。 - -型システムで行なえることには以下のようなものが存在する。 - -\begin{itemize} - \item エラーの検出 - - 文字列演算を行なう関数に整数を渡してしまったり、複雑な場合分けで境界条件を見落すなど、プログラマの不注意が型の不整合として表れる。 - - \item 抽象化 - - 型は大規模プログラムの抽象化の単位にもなる。 - 例えば特定のデータ構造に対する処理をモジュール化し、パッケージングすることができる。 - - \item ドキュメント化 - - 関数やモジュールの型を確認することにより、プログラムの理解の助けになる。 - また、型はコンパイラが実行されるたびに検査されるため、常に最新の正しい情報を提供する。 - - \item 言語の安全性 - - 例えばポインタを直接扱わないようメモリアクセスを抽象化し、データを破壊する可能性をプログラマに提供しないようにできる。 - - \item 効率性 - - そもそも、科学計算機における最初の型システムは Fortran~\ref{Backus:1978:HFI:960118.808380} などにおける整数と実数の算術式の区別だった。 - 型の導入により、ソースからコンパイラがより最適化されたコードを生成できる。 - -\end{itemize} - -型システムには多くの定義が存在する。 -型の表現能力には単純型や総称型、部分型などが存在する。 -型付けにも動的型付けや静的型付けが存在し、どの型システムを採用するかは言語の設計に依存する。 -例えば C言語では数値と文字を二項演算子 \verb/+/ で加算できるが、Haskell では加算することができない。 -これは Haskell が C言語よりも厳密な型システムを採用しているからである。 -具体的には Haskell は暗黙的な型変換を許さず、 C 言語は言語仕様として暗黙の型変換を持っている。 - -型システムを定義することはプログラミング言語がどのような特徴を持つかを決めることにも繋がる。 - -% }}} - % {{{ 依存型を持つ証明支援系言語 Agda \section{依存型を持つ証明支援系言語 Agda} diff -r 39a27b704f0c -r c0199291c58e paper/atton-master.pdf Binary file paper/atton-master.pdf has changed diff -r 39a27b704f0c -r c0199291c58e paper/atton-master.tex --- a/paper/atton-master.tex Wed Feb 08 17:39:12 2017 +0900 +++ b/paper/atton-master.tex Thu Feb 09 12:56:22 2017 +0900 @@ -124,6 +124,7 @@ \input{introduction.tex} \input{cbc.tex} \input{akasha.tex} +\input{type.tex} \input{agda.tex} \input{cbc-type.tex} \input{summary.tex} diff -r 39a27b704f0c -r c0199291c58e paper/cbc-type.tex --- a/paper/cbc-type.tex Wed Feb 08 17:39:12 2017 +0900 +++ b/paper/cbc-type.tex Thu Feb 09 12:56:22 2017 +0900 @@ -6,285 +6,10 @@ CbC で自身を証明するために依存型を利用したいが、CbC には専用の型システムが存在しない。 依存型をCbCコンパイラで扱うためにもまず現状の CbC を型付けする必要がある。 -~\ref{chapter:cbc-type}では CbC の型システムを部分型を利用して定義する。 +~\ref{chapter:cbc-type}では CbC の項が部分型で型付けできることを示す。 定義した型システムを用いて、Agda 上に DataSegment と CodeSegment の定義、CodeSegment の接続と実行、メタ計算を定義し、それらが型付けされることを確認する。 また、Agda 上で定義した DataSegment とそれに付随する CodeSegment の持つ性質を Agda で証明する。 -% {{{ 部分型付け - -\section{部分型付け} -TODO: なおす -単純型付きラムダ計算では、ラムダ計算の項が型付けされることを確認した。 -ここで、 単純型の拡張として、レコードを導入する。 - -レコードとは名前の付いた複数の値を保持するデータである。 -C 言語における構造体などがレコードに相当する。 -値を保持する各フィールド $ t_1 $ はあらかじめ定められた集合 L からラベル $ l_i$ を名前として持つ。 -例えば $ { x : Nat } $ や $ {no : 100, point 33}$ などがこれに相当する。 -なお、あるレコードの項や値に表れるラベルはすべて相異なるとする。 -レコードから値を取り出す際にはラベルを指定して値を射影する。 - -レコードの拡張の定義は以下である。 - -\begin{definition} - レコードの拡張に用いる新しい構文形式 - - \begin{align*} - t :: = ... && \text{項 :} \\ - \{l_i = t_i^{\; i \in 1 .. n}\} && \text{レコード} \\ - t.l && \text{射影} \\ - \end{align*} - - \begin{align*} - v :: = ... && \text{値 :} \\ - {l_i : v_i^{\; i \in 1..n}} && \text{レコードの値} - \end{align*} - - \begin{align*} - T :: = ... && \text{型 :} \\ - \{l_i : T_i^{\; i \in 1..n}\} && \text{レコードの型} - \end{align*} -\end{definition} - -\begin{definition} - レコードの拡張に用いる新しい評価規則 - - \begin{align*} - \{l_i = v_i^{\; i \in 1..n}.l_j \rightarrow v_j\} && \text{E-PROJRCD} \\ - \AxiomC{$t_1 \rightarrow t_1'$} - \UnaryInfC{$t_1.l \rightarrow t_1'.l$} - \DisplayProof && \text{E-PROJ} \\ - \AxiomC{$ t_j \rightarrow t_j'$} - \UnaryInfC{$ \{l_i = v_i^{i \in 1..j-1}, l_j = t_j, l_k = t_k^{k \in j+1 .. n}\} - \rightarrow - \{l_i = v_i^{i \in 1..j-1}, l_j = t_j', l_k = t_k^{k \in j+1 .. n}\} - $} - \DisplayProof && \text{E-RCD} \\ - \end{align*} -\end{definition} - -\begin{definition} - レコードの拡張に用いる新しい型付け規則 - - \begin{align*} - \AxiomC{各iに対して$ \Gamma \vdash t_i : T_i$} - \UnaryInfC{$ \Gamma \vdash \{ l_i = t_i^{\; i \in 1..n} : \{l_i : T_i^{\; i \in 1..n}\}$} - \DisplayProof && \text{T-RCD} \\ - \AxiomC{$ \Gamma \vdash t_1 : \{ l_i : T_i^{\; i \in 1.. n}\}$} - \UnaryInfC{$\Gamma \vdash t_1.lj : T_j$} - \DisplayProof && \text{T-PROJ} - \end{align*} -\end{definition} - -レコードを用いることで複数の値を一つの値としてまとめて扱うことができる。 -しかし、引数にレコードを取る場合、その型と完全に一致させる必要がある。 -例えば $ \{ x : Nat \} $ を引数に取る関数に対して $ \{ x : Nat , y : Bool\}$ といった値を適用することができない。 -しかし、直感的には関数の要求はフィールド $x $ が型 $Nat$を持つことのみであり、その部分にのみ注目すると$ \{ x : Nat , y : Bool\}$ も要求に沿っている。 - -部分型付けの目標は上記のような場合の項を許すように型付けを改良することにある。 -つまり型 $ S $ の任意の項が、型 $ T $ の項が期待される文脈において安全に利用できることを示す。 -この時、$ S $ を $ T $ の部分型と呼び、 $ S <: T $ と書く。 -これは型 $ S $ が型 $ T $よりも情報を多く持っていることを示しており、$S$は$T$の部分型である、と読む。 -$ S <: T $ の別の読み方として、型 $ T $ は型 $ S $ の上位型である、という読み方も存在する。 - -型付け関係と部分型関係をつなぐための新しい型付け規則を考える。 - -\begin{align*} - \AxiomC{$\Gamma \vdash t : S $} - \AxiomC{$ S <: T $} - \BinaryInfC{$ \Gamma \vdash t : T$} - \DisplayProof && \text {T-SUB} -\end{align*} - -この規則は $ S <: T $ ならば $ S $ 型の要素 $ t$はすべて $ T $の要素であると言っている。 -例えば、先程の $ \{ x : Nat \} $ と $ \{ x : Nat , y : Bool\}$ が $ \{ x : Nat , y : Bool\} <: \{ x : Nat \}$ となるように部分型関係を定義した時に $ \Gamma \vdash \{x=0, y=1\} : \{ x : Nat \}$ を導ける。 - -部分型関係は $ S <: T $ という形式の部分型付け式を導入するための推論規則の集まりとして定式化される。 -始めに、部分型付けの一般的な規定から考える。 -部分型は反射的であり、推移的である。 - -\begin{align*} - S <: S && \text{S-REFL} \\ - \AxiomC{$S <: U$} - \AxiomC{$U <: T$} - \BinaryInfC{$ S <: T $} - \DisplayProof && \text{S-TRANS} -\end{align*} - -これらの規則は安全代入に対する直感より正しい。 -次に、基本型や関数型、レコード型などの型それぞれに対して、その形の型の要素が期待される部分に対して上位型の要素を利用しても安全である、という規則を導入していく。 - -レコード型については型 $ T = \{ l_i : T_1 ... l_n : T_n\} $が持つフィールドが型 $ S = \{ k_1 : S_1 ... k_n : T_n\} $のものよりも少なければ $S$ を $T$の部分型とする。 -つまりレコードの終端フィールドのいくつかを忘れてしまっても安全である、ということを意味する。 -この直感は幅部分型付け規則となる。 - -\begin{align*} - \{l_i : T_i^{\; i \in 1..n+k} \} <: \{l_i : T_i^{\; i \in 1..n}\} - && \text{S-RCDWIDTH} -\end{align*} - -フィールドの多い方が部分型となるのは名前に反するように思える。 -しかし、フィールドが多いレコードほど制約が多くなり表すことのできる集合の範囲は小さくなる。 -集合の大きさで見ると明かにフィールドの多い方が小さいのである。 - -幅部分型付け規則が適用可能なのは、共通のフィールドの型が全く同じな場合のみである。 -しかし、その場合フィールドの型に部分型を導入できず、フィールドの型の扱いで同じ問題を抱えることとなる。 -そのために導入するのが深さ部分型付けである。 -二つのレコードの中で対応するフィールドの型が部分型付け関係にある時に個々のフィールドの型が異なることを許す。 -これは具体的には以下の規則となる。 - -\begin{align*} - \AxiomC{各iに対して $S_i <: T_i$} - \UnaryInfC{$\{ l_i : S_i^{\; i \in 1..n}\} <: \{l_i : T_i^{\; i \in 1..n}\}$} - \DisplayProof && \text{S-RCDDEPTH} -\end{align*} - -これらを用いて $ \{ x : \{a : Nat , b : Nat\}, y : \{m : Nat\}\}$ が $ \{x : \{ a : Nat\}, y : \{\}\}$の部分型であることは以下のように導出できる。 - -\begin{prooftree} - \AxiomC{} - \RightLabel{S-RCDWIDTH} - \UnaryInfC{$ \{a : Nat, b : Nat\} <: \{a : Nat\}$} - \AxiomC{} - \RightLabel{S-RCDWIDTH} - \UnaryInfC{$ \{m : Nat\} <: \{\}$} - \RightLabel{S-RCDDEPTH} - \BinaryInfC{$ \{ x : \{a : Nat , b : Nat\}, y : \{m : Nat\}\} <: \{x : \{ a : Nat\}, y : \{\}\}$} -\end{prooftree} - -最後に、レコードを利用する際はフィールドさえ揃っていれば順序は異なっても良いという規則を示す。 - -\begin{align*} - \AxiomC{$ \{ k_j : S_j^{\; i \in 1 .. n} \}$ は $ \{ l_i : T_i^{\; i \in 1 ..n}\} $ の並べ替えである} - \UnaryInfC{$ \{k_j : S_j^{\; j \in 1..n} \} <: \{l_i : T_i^{\; i \in 1..n }\}$} - \DisplayProof && \text{S-RCDPERM} -\end{align*} - -S-RCDPERM を用いることで、終端フィールドだけではなく任意の位置のフィールドを削ることができる。 - -レコードの部分型は定義できたので、次は関数の部分型を定義していく。 -関数の部分型は以下 S-ARROW として定義できる。 - -\begin{align*} - \AxiomC{$ T_1 <: S_1$} - \AxiomC{$ S_2 <: T_2$} - \BinaryInfC{$ S_1 \rightarrow S_2 <: T_1 \rightarrow T_2 $} - \DisplayProof && \text{S-ARROW} -\end{align*} - -前提の条件二つを見ると部分型の関係が逆転している。 -左側の条件は関数型自身の型と逆になっているため反変と言い、返り値の型は同じ向きであるため共変と言う。 -引数について考えた時、求める型よりも大きい型であれば明らかに安全に呼ぶことができるために関数の引数の型の方が上位型になる。 -返り値については関数が返す型が部分型であれば上位型を返すことができるため、関数の返り値の方が部分型になる。 - -別の見方をすると、型 $ S_1 \rightarrow S_2 $ の関数を別の型 $ T_1 \rightarrow T_2 $が期待される文脈で用いることが安全な時とは - -\begin{itemize} - \item 関数に渡される引数がその関数にとって想定外でない($ T_1 <: S_1$) - \item 関数が返す値も文脈にとって想定外でない($ S_2 <: T_2 $) -\end{itemize} - -という場合に限る。 - -% }}} - -% {{{ 部分型と Continuation based C - -\section{部分型と Continuation based C} -部分型を用いて Conituation based C の型システムを定義していく。 - -まず、DataSegment の型から定義してく。 -DataSegment 自体はCの構造体によって定義されているため、レコード型として考えることができる。 -例えばリスト~\ref{src:akasha-context}に示していた DataSegment の一つに注目する(リスト~\ref{src:type-ds})。 - -\lstinputlisting[label=src:type-ds, caption=akashaContext の DataSegment である AkashaInfo] {src/type-ds.h} -この AkashaInfo は $ \{ minHeight : unsigned\; int , maxHeight : unsigned \; int, akashaNode : AkashaNode*\}$ であると見なせる。 -CodeSegment は DataSegment を引数に取るため、DataSegment の型は CodeSegment が要求する最低限の制約をまとめたものと言える。 - -次に Meta DataSegment について考える。 -Meta DataSegment はプログラムに出現する DataSegment の共用体であった。 -これを DataSegment の構造体に変更する。 -こうすることにより、 Meta DataSegment はプログラム中に出現する DataSegment を必ず持つため、Meta DataSegment は任意の DataSegment の部分型となる。 -もしくは各 DataSegment の全てのフィールドを含むような1つの構造体でも良い。 -第~\ref{chapter:cbc-type}章における Meta DataSegment はそのように定義している。 -なお、GearsOSでは DataSegment の共用体をプログラムで必要な数だか持つ実装になっている。 - -具体的な CbC における Meta DataSegment であるContext (リスト~\ref{src:type-mds})は、 DataSegment の集合を値として持っているために明らかに DataSegment よりも多くの情報を持っている。 -\lstinputlisting[label=src:type-mds, caption=CbC の Meta DataSegment である Context] {src/type-mds.h} - -部分型として定義するなら以下のような定義となる。 - -\begin{definition} - Meta DataSegment の定義 - - \begin{align*} - Meta \; DataSegment <: DataSegment_i^{i \in N} && \text{S-MDS} - \end{align*} -\end{definition} - -なお、$N$はあるプログラムに出現するデータセグメントの名前の集合であるとする。 - -次に CodeSegment の型について考える。 -CodeSegment は DataSegment を DataSegment へと移す関数型とする。 - -\begin{definition} - CodeSegment の定義 - - \begin{align*} - DataSegment \rightarrow DataSegment && \text{T-CS} - \end{align*} -\end{definition} - -そして Meta CodeSegmentは Meta DataSegment を Meta DataSegment へと移す関数となる。 - -\begin{definition} - Meta CodeSegment の定義 - - \begin{align*} - Meta \; DataSegment \rightarrow Meta \; DataSegment && \text{T-MCS} - \end{align*} -\end{definition} - -ここで具体的なコード(リスト~\ref{src:type-cs})と比較してみる。 - -\lstinputlisting[label=src:type-cs, caption=具体的なCbCにおける CodeSegment] {src/type-cs.c} - -CodeSegment \verb/getMinHeight/ は DataSegment \verb/Allocate/ と \verb/AkashaInfo/ を引数に取っている。 -現状は \verb/Context/ も継続のために渡しているが、本来ノーマルレベルからはアクセスできないために隠れているとする。 -その場合、引数の型は $ \{ allocate : Allocate , akashaInfo : AkahsaInfo\} $ となる。 -また、返り値は構文的には存在していないが、軽量継続で渡す値は $ Context $ である。 -よって \verb/getMinHeight/ の型は $ \{ allocate : Allocate , akashaInfo : AkahsaInfo\} \rightarrow Context $ となる。 -$ Context $ の型は Meta DataSegment なので、 subtype の S-ARROW より $Context $の上位型への変更ができる。 - -$ \{ allocat; : Allocate , akashaInfo : AkahsaInfo\} $ を $X$と置いて、\verb/getMinHeignt/ を $ X \rightarrow X $ とする際の導出木は以下である。 - -\begin{prooftree} - \AxiomC{} - \RightLabel{S-REFL} - \UnaryInfC{$ X <: X $} - \AxiomC{} - \RightLabel{S-MDS} - \UnaryInfC{$ Conttext <: X$} - \RightLabel{S-ARROW} - \BinaryInfC{$ X \rightarrow Context <: X \rightarrow X$} -\end{prooftree} - -返り値部分を部分型として定義することにより、軽量継続先が上位型であればどの CodeSegment へと遷移しても良い。 -プログラムによっては遷移先は確定しているために部分型にする必要性は無いが、メタ計算やライブラリなどの遷移先が不定の場合は一度部分型として確定し、その後コンパイル時やランタイム時に包摂関係から具体型を導けば良い。 -例えばコンパイル時に解決すればライブラリの静的リンク時実行コード生成が行なえ、ランタイム時に解決すればネットワークを経由するプログラムとの接続初期化に利用できる。 -例えば、プロトコルがバージョンを持つ場合に接続先のプログラムが利用しているプロトコルと互換性があるかの判断を Context どうしの部分型関係で判断できる。 - -また、stub のみに注目すると、stub は Context から具体的なDataSegment X を取り出す操作に相当し、S-ARROWの左側の前提のような振舞いをする。 -加えて、軽量継続する際に X の計算結果を Context に格納してから goto する部分を別の Meta CodeSegment として分離すると、S-ARROWの右側の前提のような振舞いを行なう。 -このようにノーマルレベルの CodeSegment の先頭と末尾にメタ計算を接続することによってノーマルレベルの CodeSegment が型付けできる。 -型付けにDataSegment の集合としての Meta DataSegment が必須になるが、これは構造体として定義可能なためコンパイル時に生成することで CbC に組み込むことができる。 - -なお、メタ計算に利用する Meta DataSegment と Meta DataSegment も同様に型付けできる。 -ここで興味深いのはあるレベルの CodeSegment は同レベルの DataSegment において型付けされるが、一つ上の階層から見ると、下の階層のDataSegmentとして一貫して扱えることにある。 -このようにメタ計算を階層化することにより、メタ計算で拡張された計算に対しても他のメタ計算が容易に適用できる。 - -% }}} - % {{{ DataSegment の定義 \section{DataSegment の定義} まず DataSegment から定義していく。 diff -r 39a27b704f0c -r c0199291c58e paper/summary.tex --- a/paper/summary.tex Wed Feb 08 17:39:12 2017 +0900 +++ b/paper/summary.tex Thu Feb 09 12:56:22 2017 +0900 @@ -29,5 +29,5 @@ モデル検査的アプローチの展望としては、依存型を CbC コンパイラに実装し、型情報を用いた記号実行や状態の列挙を行なうシステムの構築などがある。 また、型システムの拡張としては総称型などを CbC に適用することも挙げられる。 -総称型は \verb/Java/ におけるジェネリクスや \verb/C++/ におけるテンプレートに相当し、ユーザが定義できるデータ構造の表現能力が向上する。 +多相型は \verb/Java/ におけるジェネリクスや \verb/C++/ におけるテンプレートに相当し、ユーザが定義できるデータ構造の表現能力が向上する。 他にも、CbC における型推論や推論器のコンパイラへの実装などが挙げられる。 diff -r 39a27b704f0c -r c0199291c58e paper/type.tex --- a/paper/type.tex Wed Feb 08 17:39:12 2017 +0900 +++ b/paper/type.tex Thu Feb 09 12:56:22 2017 +0900 @@ -1,905 +1,247 @@ \chapter{ラムダ計算と型システム} \label{chapter:type} -\ref{chapter:type} 章ではCbC の項の形式的な定義の一つとして、部分型を用いて CbC の CodeSegment と DataSegment を定義する。 -また、型システムの別の利用方法として命題が型で表現できる Curry-Howard 対応を利用した証明が存在するが、その利用方法については\ref{chapter:agda}章で述べる。 - - -しかし、さらに多くの要素を検証したり無限回の挿入を検証するには状態の抽象化や CbC 側に記号実行の機構を組み込んだり証明を行なう必要がある。 -CbC は直接自身を証明する機構が存在しない。 -プログラムの性質を証明するには CbC の形式的な定義が必須となる。 - - -% {{{ 型無し算術式 -\section{型無し算術式} -まず、型システムやその性質について述べるためにプログラミング言語そのものの基本的な性質について述べる。 -プログラムの構文と意味論、推論について考えるために自然数とブール値のみで構成される小さな言語を扱いながら考察する。 -この言語は二種類の値しか持たないが、項の帰納的定義や証明、評価、実行時エラーのモデル化を表現することができる。 - -この言語はブール定数 $ true $ と $ false $ 、条件式、数値定数 0 、算術演算子 $ succ $ と $ pred $ 、判定演算子 $ iszero $ のみからなる。 -算術演算子 $ succ $ は与えられた数の次の数を返し、 $ pred $ はその前の数を返す。 -判定演算子$ iszero $ は与えられた項が 0 なら $ true $ を返し、それ以外は $ false $ を返す。 -これらを文法として定義すると以下のリスト\ref{src:expr-term}のようになる。 - -\lstinputlisting[label=src:expr-term, caption=算術式の項定義] {src/expr-term.txt} - -この定義では算術式の項 $ t $ を定義している。 -$ ::= $ は項の集合の定義を表であり、$ t $ は項の変数のようなものである。 -それに続くすべての行は、構文の選択肢である。 -構文の選択肢内に存在する記号 $ t $ は任意の項を代入できることを表現している。 -このように再帰的に定義することにより、 \verb/ if (ifzero (succ 0)) then true else (pred (succ 0)) / といった項もこの定義に含まれる。 -例において、 $ succ $ 、 $ pred $ 、 $ iszero $ に複合的な引数を渡す場合は読みやすさのために括弧でくくっている。 -括弧の定義は項の定義には含んでいない。 -コンパイラなど具体的な字句をパースする必要がある場合、曖昧な構文を排除するために括弧の定義は必須である。 -しかし、今回は型システムに言及するために曖昧な構文は明示的に括弧で指示することで排除し、抽象的な構文のみを取り扱うこととする。 - -現在、項と式という用語は同一である。 -型のような別の構文表現を持つ計算体系においては式はあらゆる種類の構文を表す。 -項は計算の構文的表現という意味である。 - -この言語におけるプログラムとは上述の文法で与えられた形からなる項である。 -評価の結果は常にブール定数か自然数のどちらかになる。 -これら項は値と呼ばれ、項の評価順序の形式化において区別が必要となる。 - -なお、この項の定義においては \verb/succ true/ といった怪しい項の形成を許してしまう。 -実際、これらのプログラムは無意味なものであり、このような項表現を排除するために型システムを利用する。 +\ref{chapter:akasha} 章では CbC のモデル検査的検証アプローチとして、akasha を用いた有限の要素数の挿入時の仕様の検証を行なった。 +しかし、要素数13個分の挿入を検証しても赤黒木の挿入が必ずバランスするとは断言しづらい。 -ある言語の構文を定義する際に、他の表現かいくつか存在する。 -先程の定義は次の帰納的な定義のためのコンパクトな記法である。 - -\begin{definition} - 項の集合とは以下の条件を満たす最小の集合 $ T $ である。 - \begin{eqnarray*} - \label{eq:expr} - \{true , false , 0\} \subseteq T \\ - t_1 \in T ならば \{succ \; t_1 , pred \; t_1 , iszero \; t_1\} \subseteq T \\ - t_1 \in T かつ t_2 \in T かつ t_3 \in T ならば if \; t_1 \; then \; t_2 else \; t_3 \subseteq T - \end{eqnarray*} -\end{definition} - -まず1つめの条件は、$ T $ に属する3つの式を挙げている。 -2つめと3つめの条件は、ある種の複合的な式が $ T $ に属することを判断するための規則を表している。 -最後の「最小」という単語は $ T $ がこの3つの条件によって要求される要素以外の要素を持たないことを表している。 - -また、項の帰納的表現の略記法として、二次元の推論規則形式を用いる方法もある。 -これは論理体系を自然演繹スタイルで表現するためによく使われる。 -自然演繹による証明は\ref{chapter:agda}章内で触れるが、今回は項表現として導入する。 +そこで、 CbC の性質をより厳密に定義し、その上で証明を行なうことを考えた。 +CbC のプログラムを証明できる形に変換し、任意の回数の挿入に対しても性質が保証できるよう証明するのである。 +証明を行なう機構として注目したのが型システムである。 -\begin{definition} - 項の集合は次の規則によって定義される。 - - \begin{prooftree} - \AxiomC{$ true \in T $} - \end{prooftree} - - \begin{prooftree} - \AxiomC{$ false \in T $} - \end{prooftree} - - \begin{prooftree} - \AxiomC{$ 0 \in T $} - \end{prooftree} - - \begin{prooftree} - \AxiomC{$ t_1 \in T $} - \UnaryInfC{$ succ \; t_1 \in T$} - \end{prooftree} - - \begin{prooftree} - \AxiomC{$ t_1 \in T $} - \UnaryInfC{$ pred \; t_1 \in T$} - \end{prooftree} +\ref{chapter:type}章では型システムの概要とCbCの型システムを提案する。 +また、依存型を用いた実際の証明手法については~\ref{chapter:agda}章で解説する。 - \begin{prooftree} - \AxiomC{$ t_1 \in T $} - \UnaryInfC{$ iszero \; t_1 \in T$} - \end{prooftree} - - \begin{prooftree} - \AxiomC{$ t_1 \in T $} - \AxiomC{$ t_2 \in T $} - \AxiomC{$ t_3 \in T $} - \TrinaryInfC{$ if \; t_1 \; then \; t_2 \; else \; t_3 \in T$} - \end{prooftree} - -\end{definition} - -最初の$ true, \; false, \; 0 $の3つ規則は再帰的定義の1つめの条件と同じである。 -それ以外の4つの規則は再帰的定義の2つめと3つめの条件と同じである。 -それぞれの規則は「もし線の上に列挙して前提が成立するのならば、線の下の結論を導出できる」と読む。 -$ T $ がこれらの規則を満たす最小の集合である事実は明示的に述べられない。 - -言語の構文は定義できたので、次は項がどう評価されるかの意味論について触れていく。 -意味論の形式化には操作的意味論や表示的意味論、公理的意味論やゲーム意味論などがあるが、ここでは操作的意味論について述べる。 -操作的意味論とは、言語の抽象機械を定義することにより言語の振舞いを規程する。 -この抽象機械が示す抽象とは、扱う命令がプロセッサの命令セットなどの具体的なものでないことを表している。 -単純な言語の抽象機械における状態は単なる項であり、機械の振舞いは遷移関数で定義される。 -この関数は各状態において項の単純化ステップを実行して次の状態を与えるか、機械を停止させる。 -ここで項 $ t $ の意味は、$ t $ を初期状態として動き始めた機械が達する最終状態である。 - -なお、一つの言語に複数の操作的意味論を与えることもある。 -例えば、プログラマが扱う項に似た機械状態を持つ意味論の他に、コンパイラの内部表現やインタプリタが扱う意味論を定義する。 -これらの振舞いが同じプログラムを実行した時に何かしらの意味であれば、結果としてその言語の実装の正しさを証明することに繋がる。 - -まずはブール式のみの操作的意味論を定義する。 - -\begin{definition} - ブール値(B) - - 項 - \begin{align*} - t ::= && \text{項} \\ - true && \text{定数真} \\ - false && \text{定数偽} \\ - if \; t \; then \; t \; else \; t && \text{条件式} - \end{align*} +% {{{ 型システムとは +\section{型システムとは} +型システムとは、計算する値を分類することにでプログラムがある種の振舞いを行なわないことを保証する機構の事である\cite{Pierce:2002:TPL:509043}\cite{pierce2013型システム入門プログラミング言語と型の理論}。 +ある種の振舞いとはプログラム中の評価不可能な式や、言語として未定義な式などが当て嵌まる。 +例えば、gcc や clang といったコンパイラは関数定義時に指定された引数の型と呼び出し時の値の型が異なる時に警告を出す。 +この警告は関数が受けつける範囲以外の値をプログラマが渡してしまった場合などに有効に働く。 +加えて、関数を定義する側も受け付ける値の範囲を限定できるため関数内部の処理を記述しやすい。 - 値 - \begin{align*} - v ::= && \text{値} \\ - true && \text{真} \\ - false && \text{偽} - \end{align*} - - 評価 - \begin{align*} - if \; true \; then \; t_2 \; else t_3 \rightarrow t_2 && \text{(E-IFTRUE)} \\ - if \; false \; then \; t_2 \; else t_3 \rightarrow t_3 && \text{(E-IFFALSE)} \\ - \AxiomC{$ t_1 \rightarrow t_1'$} - \UnaryInfC{$ if \; t_1 \; then \; t_2 \; else \; t_3 \rightarrow if \; t_1' \; then t_2 \; else \;t_3 $} - \DisplayProof - && \text{(E-IF)} - \end{align*} - -\end{definition} - -評価の最終結果になりえる項である値は定数 $ true $ と $ false $ のみである。 -評価の定義は評価関係の定義である。 -評価関係 $ t \rightarrow t' $ は「$ t $ が1ステップで $ t' $ に評価される」と読む。 -直感的には抽象機械の状態が $ t $ ならば $ t' $ が手に入るという意味である。 - -評価関係は3つあるが、2つは前提を持たないため、2つの公理と1つの規則から成る。 -1つめの規則 E-IFTRUE の意味は、評価の対象となる項の条件式が定数 $ true $ である時に、then 節にある $ t_2 $ を残して他の全ての項を捨てるという意味である。 -E-EIFFALSE も同様に条件式が $ false $ の時に $ t_3 $ のみを残す。 -3つ目の規則 E-IF は条件式の評価である。 -条件式 $ t $ が $ t'$ に評価されうるのならば then 節と else 節を変えずに条件部のみを評価する。 - -評価の定義から分かることの中に、if の中の then節 と else 節は条件部より先に評価されないことがある。 -よって、この言語は条件式の評価に対し条件部から評価が優先されるという評価戦略を持つことが分かる。 - -\begin{definition} -推論規則のインスタンスとは、規則の結論や前提に対し、一貫して同じ項による書き換えを行なったものである。 -\end{definition} - -例えば、 -$ if \; true \; then \; true \; else \; ( \;if \; false \; then \; false \; else \; false) $ -は E-IFTRUE のインスタンスであり、 E-IFTRUEの $ t_2 $ が \verb/true/ かつ、 -$ t_3 $ が $ if \; false \; then \; false \; else \; false \;$ の時に相当する。 - -\begin{definition} -1ステップ評価関係 $ \rightarrow $ とは、3つの評価の規則を満たす、項に関する最小の二項関係である。 -$ (t, \; t') $ がこの関係の元である時、「評価関係式 $ t \rightarrow t'$ は導出可能である」と言う。 -\end{definition} - -ここで「最小」という言葉が表れるため、評価関係式 $ t \rightarrow t'$ が導出可能である時かつその時に限り、その関係式は規則によって正当化される。 -すなわち評価関係式は公理 E-IFTRUE か E-IFFALSE 、前提が成り立つ時の E-IF のインスタンスとなる。 -与えられた評価関係式が導出可能であることを証明するには、葉が E-IFTRUE か E-IFFALSE であり、内部ノードのラベルが E-IF のインスタンスである導出木が示せれば良い。 -例えば以下の略記の元 $ if \; t \; then \; false \; then \; false \rightarrow if \; u \; then \; false \; else \; false $ の導出可能性は以下のような導出木によって示せる。 +型システムで行なえることには以下のようなものが存在する。 \begin{itemize} - \item $ s = if \; true \; then \; false \; else \; false $ - \item $ t = if \; s \; then \; true \; else \; true $ - \item $ u = if \; false \; then \; true \; else \; true $ + \item エラーの検出 + + 文字列演算を行なう関数に整数を渡してしまったり、複雑な場合分けで境界条件を見落すなど、プログラマの不注意が型の不整合として表れる。 + + \item 抽象化 + + 型は大規模プログラムの抽象化の単位にもなる。 + 例えば特定のデータ構造に対する処理をモジュール化し、パッケージングすることができる。 + + \item ドキュメント化 + + 関数やモジュールの型を確認することにより、プログラムの理解の助けになる。 + また、型はコンパイラが実行されるたびに検査されるため、常に最新の正しい情報を提供する。 + + \item 言語の安全性 + + 例えばポインタを直接扱わないようメモリアクセスを抽象化し、データを破壊する可能性をプログラマに提供しないようにできる。 + + \item 効率性 + + そもそも、科学計算機における最初の型システムは Fortran~\ref{Backus:1978:HFI:960118.808380} などにおける整数と実数の算術式の区別だった。 + 型の導入により、ソースからコンパイラがより最適化されたコードを生成できる。 + \end{itemize} - -\begin{prooftree} - \AxiomC{} - \RightLabel{E-IFTRUE} - \UnaryInfC{ $ s \rightarrow true $ } - \RightLabel{E-IF} - \UnaryInfC{ $ t \rightarrow u $} - \RightLabel{E-IF} - \UnaryInfC{ $ if \; t \; then \; false \; then \; false/ \rightarrow if \; u \; then \; false \; else \; false $} -\end{prooftree} - -1ステップ評価関係は与えられた項に対して抽象機械の状態遷移を定義する。 -この時、機械がそれ以上ステップを進められない時にそれが最終結果となる。 - -\begin{definition} - 正規形 - - 項 $ t $ が正規形であるとは、$ t \rightarrow t'$となる評価規則が存在しないことである。 -\end{definition} - -この言語において $ true $ や $ false $ は正規形である。 -逆に言えば、構文的に正しい if が用いられている場合は評価することが可能なため正規形ではない。 -極端に言えばこの言語における全ての値は正規形なのである。 -しかし、他の言語における値は一般的に正規形ではない。 -実のところ、値でない正規形は実行時エラーとなって表れる。 - -実際にこの言語に自然数を導入し、値では無い正規形を確認していく。 - - -\begin{definition} - 算術式BN (B の拡張) の項 - \begin{align*} - t ::= && \text{項} \\ - true && \text{定数真} \\ - false && \text{定数偽} \\ - if \; t \; then \; t \; else \; t && \text{条件式} \\ - 0 && \text{定数ゼロ} \\ - succ \; t && \text{後者値} \\ - pred \; t && \text{前者値} \\ - iszero \; t && \text{ゼロ判定} - \end{align*} -\end{definition} +型システムには多くの定義が存在する。 +型の表現能力には単純型や総称型、部分型などが存在する。 +型付けにも動的型付けや静的型付けが存在し、どの型システムを採用するかは言語の設計に依存する。 +例えば C言語では数値と文字を二項演算子 \verb/+/ で加算できるが、Haskell では加算することができない。 +これは Haskell が C言語よりも厳密な型システムを採用しているからである。 +具体的には Haskell は暗黙的な型変換を許さず、 C 言語は言語仕様として暗黙の型変換を持っている。 -\begin{definition} - 算術式BN の値 - \begin{align*} - v ::= && \text{値} \\ - true && \text{真} \\ - false && \text{偽} \\ - nv && \text{数値} \\ - \end{align*} -\end{definition} - -\begin{definition} - 算術式BNの数値 - \begin{align*} - nv ::= && \text{数値} \\ - 0 && \text{ゼロ} \\ - succ nv && \text{後者値} - \end{align*} -\end{definition} - -\begin{definition} - 算術式BNの評価($ t \rightarrow t' $) - \begin{align*} - if \; true \; then \; t_2 \; else t_3 \rightarrow t_2 && \text{(E-IFTRUE)} \\ - if \; false \; then \; t_2 \; else t_3 \rightarrow t_3 && \text{(E-IFFALSE)} \\ - \AxiomC{$ t_1 \rightarrow t_1'$} - \UnaryInfC{$ if \; t_1 \; then \; t_2 \; else \; t_3 \rightarrow if \; t_1' \; then t_2 \; else \;t_3 $} - \DisplayProof && \text{(E-IF)} \\ - \AxiomC{$ pred \; 0 \rightarrow 0$} - \DisplayProof && \text{(E-PREDZERO)} \\ - \AxiomC{$ pred \; (succ \; nv_1) \rightarrow nv_1$} - \DisplayProof && \text{(E-PREDSUCC)} \\ - \AxiomC{$ t_1 \rightarrow t_1'$} - \UnaryInfC{$ pred \; t_1 \rightarrow pred \; t_1'$} - \DisplayProof && \text{(E-PRED)} \\ - \AxiomC{$ iszero \; 0 \rightarrow true$} - \DisplayProof && \text{(E-ISZEROZERO)} \\ - \AxiomC{$ iszero \; (succ \; nv_1) \rightarrow false$} - \DisplayProof && \text{(E-ISZEROSUCC)} \\ - \AxiomC{$ t_1 \rightarrow t_1'$} - \UnaryInfC{$ iszero \; t_1 \rightarrow iszero \; t_1'$} - \DisplayProof && \text{(E-ISZERO)} \\ - \end{align*} -\end{definition} - -今回値の定義に数値を表す構文要素が追加されている。 -数は0かある数に後者関数を適用したもののどちらかである。 -評価規則 E-PREDZERO、E-PREDSUCC、E-ISZEROZERO、E-ISZEROSUCC は演算 \verb/pred/ と \verb/iszero/ が数に適用された時にどう振る舞うかを定義している。 -E-SUCC 、 E-PRED 、 E-ISZERO の合同規則も E-IF のように部分項から先に評価することを示している。 - -数値の構文要素(nv)はこの定義によって重要な役割をはたす。 -例えば、 E-PREDSUCC 規則が適用できる項は任意の項 $ t $ ではなく数値 $nv_1$である。 -これは $ pred \; (succ \; (pred \; 0)) $ を $ pred 0 $ に評価できないことを意味する。 -なぜなら $ pred \; 0 $ は数値に含まれないからである。 - -ここで言語の操作的意味論について考える時、すべての項に関する振舞いを定義する必要がある。 -すべての項には $ pred \; 0 $ や $ succ false $ のような項も含まれる。 -しかし、 $ succ $ を $ false $ に適用する評価結果は定義されていないため、 $ succ \; false $ は正規形である。 -このような、正規形であるが値でない項は行き詰まり状態であるという。 -つまり、実行時エラーとは行き詰まり状態の項を指す。 -直感的な解釈としてはプログラムが無意味な状態になったこと示しておい、操作的意味論が次に何も行なえないことを特徴付けているのである。 -プログラング言語において実行時エラーはセグメンテーションフォールトや不正な命令などいくつかのものが挙げられるが、型システムを考える際にはこれらのエラーは行き詰まり状態という単一の概念で表す。 +型システムを定義することはプログラミング言語がどのような特徴を持つかを決めることにも繋がる。 % }}} % {{{ 単純型 +\section{単純型} +単純型とは値の型と関数型のみで構成される型システムのことである。 +とある値はとある型に属する。 +例えばリテラル \verb/true/ は \verb/bool/ 型に属するし、\verb/10/ は \verb/int/ 型に属する。 -\section{単純型} -先程定義した算術式には $ pred \; false $ のようなこれ以上評価できない行き詰まり状態が存在する。 -項を実際に評価する前に評価が行き詰まり状態にならないことを保証したい。 -そのために、自然数に評価される項とブール値に評価される項とを区別する必要がある。 -項を分類するために2つの型 Nat と Bool を定義する。 - -ここで、項$t$が型 $T$を持つ、という表現を用いた場合、$t$を評価した結果が明らかに適切な形の値になることを意味する。 -明らかに、という意味は項を実行することなく静的に分かるという意味である。 -例えば項 $ if \; true \; then \; false \; else \; true $ は Bool 型を持ち、$ pred \; (succ \; (succ \; 0)) $ はNat 型を持つ。 -しかし、項の型の分析は保守的であり、$ if \; true \; then \; 0 \; else \; false $ のような項は実際には行き詰まりにならないが型を持てない。 +また、関数は値を取って値を返す処理と考えることで「型を取って型を返す型」 $ \rightarrow $ を持つ。 +例を上げると \verb/int/ を取って \verb/int/ を返す関数fは $ \text{int} \rightarrow \text{int} $ 型に属する。 +型システムにおいて項が型付けされるのならば、関数が所望の型を持つ値以外に適用されることは無い。 +例を上げると、関数f が \verb/f(true)/ のように \verb/bool/ 型の値へと適用されることは無い。 -算術式のための型付け関係は $ t : T $ と書き、項に型を割り当てる推論規則の集合によって定義される。 -具体的な数値とブール値に関する拡張は以下である。 +関数型で複数の引数を表現することは「関数型を返す関数型」を考えることで実現できる。 +例えば \verb/int/ と \verb/bool/ を取って \verb/string/ を返す型は $ \text{int} \rightarrow \text{bool} \rightarrow \text{string} $型に属する。 +$ \rightarrow $ は右結合的であり、$ \text{int} \rightarrow \text{bool} \rightarrow \text{string} $ は $ \text{int} \rightarrow (\text{bool} \rightarrow \text{string}) $ と読む。 + +% }}} -\begin{definition} - NB(型付き) の新しい構文形式 - \begin{align*} - T ::= && \text{型 :} \\ - Bool && \text{ブール型} \\ - Nat && \text{自然数型} \\ - \end{align*} -\end{definition} +% {{{ レコード型 +\section{レコード型} +データ型には多くの種類が存在する。 +ユーザが定義可能な型と区別するために言語が用意している型をプリミティブ型を呼ぶことにする。 +C 言語におけるプリミティブ型には \verb/int/ や \verb/char/ といった型がある。 +C 言語にはプリミティブ型以外にも、ユーザが定義可能な型が存在する。 +例えば構造体は複数の値を持つような値を取り扱うような型である。 +ここで構造体に対して「構造体型」という一つの型を用意した場合、複数の構造体の区別ができなくなる。 +よって、構造体に型を付けるなら「何を内部に持っているのか」を覚えているような型でなくてはならない。 -\begin{definition} - NB(型付き)の型付け規則 - \begin{align*} - true : Bool && \text{T-TRUE} \\ - false : Bool && \text{T-FALSE} \\ - \AxiomC{$t_1 : Bool$} - \AxiomC{$t_2 : T$} - \AxiomC{$t_3 : T$} - \TrinaryInfC{$if \; t_1 \; then \; t_2 \; else \; t_3 : T$} - \DisplayProof && \text{T-IF} \\ - 0 : Nat && \text{T-ZERO} \\ - \AxiomC{$t_1 : Nat$} - \UnaryInfC{$ succ \; t_1 : Nat $} - \DisplayProof && \text{T-SUCC} \\ - \AxiomC{$t_1 : Nat$} - \UnaryInfC{$ pred \; t_1 : Nat $} - \DisplayProof && \text{T-PRED} \\ - \AxiomC{$t_1 : Nat$} - \UnaryInfC{$ iszero \; t_1 : Bool $} - \DisplayProof && \text{T-BOOL} - \end{align*} -\end{definition} +ここでレコード型という型を導入する。 +レコード型は複数の型を持ちえる型であり、内部に持っている値にはユニークな名前がラベルとして付いている。 +% TODO C の構造体 +レコード型の値を構成する際には、内部に格納する値を全て与えることで構成できる。 +% TODO C の構造体の初期化 +レコード型から値を取り出す際にはラベル名を用いた射影を利用する。 +C 言語では構造体の後に \verb/./ キーワードを付けた後にラベル名を指定する。 -T-TRUE と T-FALSE はブール定数に Bool 型を割り当てている。 -T-IFは条件式の部分にBool型を、部分式に関しては同じ型を要求している。 -これは同じ変数 $ T $ を二回使用することで制約を表している。 +これで構造体に対する型付けができた。 +% }}} + +% {{{ 部分型付け -また、数に関しては T-ZERO は Nat 型を $ 0 $ に割り当てている。 -T-SUCC と T-PRED は $ t_1 $ が Nat である時に限り Nat 型となる。 -同様に、 T-ISZERO は $ t_1 $ が Nat である時に Bool となる。 - -\begin{definition} -算術式のための型付け関係とは、NBにおける規則のすべてのインスタンスを満たす、項と型の二項関係である。 -項$ t $ に対してある型 $ T $ が存在して $ t : T $ である時、 $ t $ は型付け可能である(または正しく型付けされている)という。 -\end{definition} - -型推論をを行なう時、$succ t_1$という項が何らかの型を持つならばそれは Nat 型である、といった言及を行なう。 -型付け関係を逆転させた補題を定義することで型推論の基本的なアルゴリズムを考えることができる。 -なお、逆転補題は型付け関係の定義により直ちに成り立つ。 +\section{部分型付け} +レコードを用いることで複数の値を一つの値としてまとめて扱うことができる。 +しかし、関数が引数にレコードを取る場合、その型と完全に一致させる必要がある。 +例えば $ \{ x : Nat \} $ を引数に取る関数に対して $ \{ x : Nat , y : Bool\}$ といった値を適用することができない。 +しかし、直感的には関数の要求はフィールド $x $ が型 $Nat$を持つことのみであり、その部分にのみ注目すると$ \{ x : Nat , y : Bool\}$ も要求に沿っている。 -\begin{lemma} - 型付け関係の逆転 - \begin{enumerate} - \item $ true : R $ ならば $ R = Bool $ である - \item $ false : R $ ならば $ R = Bool $ である - \item $ if \; t_1 \; then \; t_2 \; else \; t_3 : R $ ならば $ t_1 : Bool $ かつ $ t_2 : R $ かつ $ t_3 : R $ である。 - \item $ 0 : R $ ならば $ R = Nat $ である - \item $ succ \; t_1 : R $ ならば $ R = Nat $ かつ $ t_1 : Nat $ である - \item $ pred \; t_1 : R $ ならば $ R = Nat $ かつ $ t_1 : Nat $ である - \item $ iszero \; t_1 : R $ ならば $ R = Bool $ かつ $ t_1 : Nat $ である - \end{enumerate} -\end{lemma} +ここで、部分型という型を導入する。 +部分型は上記のような場合の項を許すように型付けを緩めることである。 +つまり型 $ S $ を持つ値が、型 $ T $ の値が期待される文脈において安全に利用できることを示す。 +この時、$ S $ を $ T $ の部分型と呼び、 $ S <: T $ と書く。 +これは型 $ S $ が型 $ T $よりも情報を多く持っていることを示しており、$S$は$T$の部分型である、と読む。 +$ S <: T $ の別の読み方として、型 $ T $ は型 $ S $ の上位型である、という読み方も存在する。 -逆転補題は型付け関係のための生成補題と呼ばれることもある。 -なぜならば、与えられた型付け判断式に対してその証明がどのように生成されたかを示すからである。 +値に関する部分型は「とあるデータ型$T$よりも$S$の方が持っている情報が多いなら、$S$は$T$として振る舞っても良い」と定義できる。 +フィールドの多い方が部分型となるのは名前に反するように思える。 +しかし、フィールドが多いほど制約が多くなり、表すことのできる集合の範囲は小さくなる。 +集合の大きさで見ると明かにフィールドの多い方が小さいのである。 -型無し算術式の評価導出のように型付けも導出可能であり、それも規則のインスタンスの木である。 -型付け関係に含まれる二つ組 $(t, \; T)$は $ t : T $ を結論とする型付け導出により正当化される。 -例えば $ if \; (iszero \; 0) \; then \; 0 \; else \; (pred \; 0) : Nat $ の型付け判断の導出木である。 +また、任意の型$T$に対して $ T <: T $ である。 +これは「$T$ は $ T$ として振る舞っても良い」ことを示しているので自明である。 + +関数の部分型は以下のように定義できる。 \begin{prooftree} - \AxiomC{} - \RightLabel{T-ZERO} - \UnaryInfC{$ 0 : Nat$} - \RightLabel{T-ISZERO} - \UnaryInfC{$ iszero \; 0 : Bool$} - \AxiomC{} - \RightLabel{T-ZERO} - \UnaryInfC{$ 0 : Nat$} - \AxiomC{} - \RightLabel{T-ZERO} - \UnaryInfC{$ 0 : Nat$} - \RightLabel{T-PRED} - \UnaryInfC{$ pred \; 0 : Bool$} - \RightLabel{T-IF} - \TrinaryInfC{ if \; (iszero \; 0) \; then \; 0 \; else \; (pred \; 0) : Nat } + \AxiomC{$ T_1 <: S_1$} + \AxiomC{$ S_2 <: T_2$} + \BinaryInfC{$ S_1 \rightarrow S_2 <: T_1 \rightarrow T_2 $} \end{prooftree} -項その型付けの定義より、型システムが行き詰まり状態にならないことを示す。 -その証明は指向定理と保存定理によって証明する。 - -\begin{itemize} - \item 進行とは、正しく型付けされた項は行き詰まり状態では無いことである - \item 保存とは、評価可能な正しく型付けされた項は評価後も正しく型付けされていることである。 -\end{itemize} - -型システムがこれらの性質を持つ時、正しく型付けされた項は行き詰まり状態になりえない。 - -進行定理の証明の為に Bool 型と Nat 型の標準形(それらの型を持つ正しく型付けされた値)を示す。 - -\begin{lemma} - 標準形 - - \begin{enumerate} - \item $ v $ が $Bool$ 型の値ならば $v$ は $true$ または $false$ である。 - \item $v$ が $Nat$ 型の値ならば、$0$ もしくは $Nat$ に対して $succ$ を適用した値である。 - \end{enumerate} -\end{lemma} - -標準形の証明に関しては値における構造的帰納法を用いる。 -この言語における値とは $ true $ と $false $ と $ 0$ と $ succ \; nv$ のいずれかの形をしている。 -Bool型に関して注目した時、 $ true $ と $ false $ は定義によって正しい。 -$ 0 $ と $ succ \; nv $ に関しては逆転補題より Nat 型を持つため、Bool型を持つ値は $ true $ と $ false $ のどちらかとなる。 -Nat についても同様である。 - -\begin{theorem} -進行 - -$ t$ が正しく型付けされたと仮定すると、$ t$ は値であるか、またはある $t'$ が存在して$ t \rightarrow t' $ となる。 -\end{theorem} - -証明は $ t : T $ の導出に関する帰納法による。 -T-TRUE 、 T-FALSE 、 T-ZERO の場合は$t$が値であることより成立する。 - -T-IF の場合、帰納法の仮定により $ t1 $ は値であるか、$t_1'$ が存在して $ t_1 \rightarrow t_1'$ を満たす。 -$ t_1 $ が値ならば、標準形補題により $ true $ か $ false $ であり、その場合は E-IFTRUE か E-IFFALSE が適用可能である。 -一方 $ t_1 \rightarrow t_1' $ ならば E-IF が適用できる。 +これは「仮定{$ T_1 <: S_1$ と$ S_2 <: T_2$ が成り立つ時、$ S_1 \rightarrow S_2 <: T_1 \rightarrow T_2 $ が成り立つ」と読む。 +この部分型は引数の型と返り値の部分型について述べているために少々複雑である。 -T-SUCC の場合も帰納法の仮定により $ t1 $ は値であるか、$t_1'$ が存在して $ t_1 \rightarrow t_1'$ を満たす。 -$ t_1 $ が値ならば標準形補題により数値でなければならず、その場合 $ t $ も数値であるため成り立つ。 -一方 $ t_1 \rightarrow t_1' $ ならば E-SUCC が適用できる。 - -T-SUCC の場合も同様で、 $ t_1 $ が値ならば標準形補題により数値でなければならず、その場合 E-PREDZERO か E-PREDSUCC が使える。 -$ t_1 \rightarrow t_1' $ ならば E-PRED が適用できる。 - -T-ISZERO の場合も値ならば標準形補題により $ t_1 $ は数値であり、どちらの場合でも E-ISZEROZERO と E-ISZEROSUCC が適用できる。 -$ t_1 \rightarrow t_1' $ ならば E-ISZERO が適用できる。 - -\begin{theorem} -保存 - -$ t : T $ かつ $ t \rightarrow t' $ ならば $ t' : T $ となる。 -\end{theorem} - -保存定理も $ t : T $ の導出に関する帰納法によって導ける。 -帰納法の各ステップにおいて全ての部分導出に関して所望の性質が成り立つと仮定し、導出の最後の規則についての場合分けで証明を行なう。 - -導入の最後の規則がT-TRUE の場合、その規則の形から $ t $ は定数 $ true $ でなければならず、 $ T $ は $ Bool$ となる。 -そして $ t $ は値であるためにどのような $ t' $ も存在せず、定理の要求は満たされる。 -T-FALSE と T-ZERO の場合も同様である。 +まず、引数部分に注目する。 +上位型の関数の引数は $ T_1 $ である。 +引数に対する仮定は部分型関係$ T_1 <: S_1$である。 +これは上位型関数の方が部分型となっており、大きい。 +そして導かれる部分型の引数の型は $ S_1$ である。 +つまり、「大きい型 $T_1$を要求する関数は小さい型 $S_1$ を要求する関数として使って良い」ということである。 +具体的には $ T_1 $ のレコードをいくつか削って $S_1$まで小さくすれば良い。 -導入の最後の規則 T-IF の場合は、$ t $ はある $ t_1, \; t_2 \; t_3 $ に対して $ if \; t_1 then t_2 else t_3 $ という形となる。 -さらに $ t_1 : Bool $ と $ t_2 : T $ と $ t_3 : T $ となる部分導出がある。 -ここで if を持つ評価規則において $ t \rightarrow t'$ を導入できる規則は E-IFTRUE と E-IFFALSE と E-IF のみである。 -それぞれの場合について別々に場合分けをして考える。 - -\begin{itemize} - \item E-IFTRUE の場合(E-IFFALSE も同様) - - $ t \rightarrow t' $ が E-IFTRUE を使った導出ならば、 $ t_1$ は $ true $ であり、結果の項 $ t' $ は $ t_2 $ となる。 - このことより $ t_2 : T $ であることが分かるため、条件を満たす。 - - \item E-IF の場合 - - 場合分け T-IF の仮定より $ t_1 : Bool $が結論となる、部分導出が得られる。 - 帰納法の仮定を部分導出に適用して $ t_1' : Bool $ とし、 $ t_2 : T $ と $ t_3 : T $ を合わせると規則 T-IF が適用できる。 - T-IF を適用すると $ if \; t_1' \; then \; t_2 \; else \; t_3$となり、$ t' : T $ が成り立つ。 -\end{itemize} - -T-SUCC が導入の最後であれば、 $ t \rightarrow t'$ を導くためには E-SUCC のみであり、この形から $ t_1 \rightarrow t_1'$が分かる。 -$ t_1 : Nat $ であることも分かるため、帰納法の仮定より $ t_1' : Nat $ が得られる。 -この時 T-SUCC が適用できるため $ succ \; t_1 : Nat$となって $ t' : T $ が成り立つ。 -T-PRED も同様である。 +次に返り値部分に注目する。 +上位型の関数の返り値は $T_2$ である。 +返り値に対する仮定は部分型関係$ S_2 <: T_2$であり、引数と逆になっている。 +これは上位型関数の方が上位型となっており、小さい。 +つまり、「小さい型 $ T_2 $ を返す関数は、大きい型 $S_2$ を返す関数として使っても良い」ということである。 +具体的には $ T_2 $ のレコードが $S_2$ に全て格納できることを意味する。 % }}} -% {{{ 型なしラムダ計算 -\section{型なしラムダ計算} -計算とは何か、エラーとは何か、を算術式を定義することによって示してきた。 -また、型を導入することにより行き詰まり状態を回避することも示した。 -ここで、プログラミング言語における計算を形式的に定義していく。 -プログラミング言語は複雑だが、その計算はある本質的な仕組みからの派生形式として定式化可能であることを Peter Ladin が示した~\cite{Landin64}。 -この時 Landin が使った本質的な仕組みとしての核計算がラムダ計算であった。 -ラムダ計算は Alonzo Church が発明した形式的体系の一つである~\cite{GlossarWiki:Church:1941}。 -ラムダ計算では全ての計算が関数定義と関数適用の基本的な演算に帰着される。 -ラムダ計算はプログラミング言語の機能の仕様記述や、言語設計と実装、型システムの研究に多く使われている。 -この計算体系の重要な点は、ラムダ計算内部で計算が記述できるプログラミング言語であると同時に、それ自身について厳格な証明が可能な数学的対象としてみなせる点にある。 - -ラムダ計算はいろいろな方法で拡張できる。 -数や組やレコードなどはラムダ計算そのもので模倣することができるが、記述が冗長になってしまう。 -それらの機能のための具体的な特殊構文を加えることは言語の利用者の視点で便利である。 -他にも書き換え可能な参照セルや非局所的な例外といった複雑な機能を表現することもできるが、膨大な変換を用いなければモデル化できない。 -それの機能を言語として備えた拡張に ML や Haskell~\cite{haskell-sigplan} といったものがある。 - -ラムダ計算(または $ \lambda $ 計算) とは、関数定義と関数適用を純粋な形で表現する。 -ラムダ計算においてはすべてが関数である。 -関数によって受け付ける引数も関数であり、関数が返す結果もまた関数である。 - -ラムダ計算の項は変数と抽象と適用の3種類の項からなり、以下の文法に要約される。 -変数 $ x $ は項であり、項 $ t_1 $ から変数 $ x $ を抽象化した $ \lambda x . t_1 $ も項であり、項 $ t_1 $ を他の項 $ t_2 $ に適用した $ t_1 t_2 $ も項である。 +% {{{ 部分型と Continuation based C -\begin{multline*} - t ::= \\ - x \\ - \lambda x . t \\ - t \, t \\ -\end{multline*} - -ラムダ計算において関数適用は左結合とする。 -つまり、 $ s \, t \, u $ は $ (s \, t) \, u $ となる。 - -また、抽象の本体はできる限り右側へと拡大する。 -例えば $ \lambda x . \; \lambda y . \; x \, y \, x $ は$ \lambda x . (\lambda . y ((x \, y) \, x)) $ となる。 +\section{部分型と Continuation based C} +TODO なおす +部分型を用いて Conituation based C の型システムを定義していく。 -ラムダ計算には変数のスコープが存在する。 -抽象 $ \lambda x . t $ の本体 $ t $ の中に変数 $ x $ がある時、 $ x $ の出現は束縛されていると言う。 -同様に、 $ \lambda x $ は $ t $ をスコープとする束縛子であると言う。 -なお、 $ x $ を囲む抽象によって束縛されていない場所の $ x $ の出現は自由であると言う。 -例えば $ x \; y $ や $ \lambda y . \; x \; y $ における $ x $ の出現は自由だが、 $ \lambda x . x $ や $ \lambda z . \lambda x . \lambda y . x (y \; z) $ における $ x $ の出現は束縛されている。 -$ (\lambda x . x) \;x $ においては、最初の $ x $ の出現は束縛されているが、2つ目の出現は自由である。 - -ラムダ計算において、計算とは引数に対する関数の適用である。 -抽象に対して項を適用した場合、抽象の本体に存在する束縛変数に適用する項を代入したもので書き換える。 -図式的には - -\begin{equation*} - (\lambda x . t_{12}) t_2 \rightarrow [ x \mapsto t_2] t_{12} -\end{equation*} +まず、DataSegment の型から定義してく。 +DataSegment 自体はCの構造体によって定義されているため、レコード型として考えることができる。 +例えばリスト~\ref{src:akasha-context}に示していた DataSegment の一つに注目する(リスト~\ref{src:type-ds})。 -と記述する。 -ここで $ [ x \mapsto t_2] t_{12} $ とは、$ t_12 $ 中の自由な $ x $ を全て $ t_2 $ で置換した項を意味する。 -例えば、 $ (\lambda x . x) \; y $ は $ y $ となり、項 $ (\lambda x . x (\lambda x . x)) (y \; z) $ は $ y \; z \; (\lambda x . x) $ となる。 - -なお、 $ (\lambda x . t_{12}) t_2 $ という形の項を簡約基(redex, reducible expression) と呼び、上記の規則で簡約基を置換する操作をベータ簡約と呼ぶ。 -ラムダ計算のための評価戦略には数種類の戦略がある。 - -\begin{itemize} - \item 完全ベータ簡約 - - 任意の簡約基がいつでも簡約されうる。 - つまり項の中からどの順番で簡約しても良い。 - - \item 正規順序簡約 - - 最も左で最も外側の簡約基が最初に簡約される。 - - \item 名前呼び - - 正規順序の中でも抽象の内部での簡約を許さない。 - 名前呼びの変種は Algol-60 や Haskell で利用されている。 - なお、Haskell においては必要呼びという最適化された変種を利用している。 - - \item 値呼び +\lstinputlisting[label=src:type-ds, caption=akashaContext の DataSegment である AkashaInfo] {src/type-ds.h} +この AkashaInfo は $ \{ minHeight : unsigned\; int , maxHeight : unsigned \; int, akashaNode : AkashaNode*\}$ であると見なせる。 +CodeSegment は DataSegment を引数に取るため、DataSegment の型は CodeSegment が要求する最低限の制約をまとめたものと言える。 - ほとんどの言語はこの戦略を用いている。 - 基本的には最も左の簡約基をを簡約するが、右側が既に値(計算が終了してもう簡約できない閉じた項)になっている簡約基のみを簡約する。 -\end{itemize} - -値呼び戦略は関数の引数が本体で使われるかに関わらず評価され、これは正格と呼ばれる。 -名前呼びなどの非正格な戦略は引数が使われる時のみ評価され、これは遅延評価とも呼ばれる。 - -ラムダ計算において、複数の引数は、関数を返り値として返す高階関数として定義できる。 -項 $ s $ が二つの自由変数 $ x $ と $ y $ を含むとすれば、 $ \lambda x . \lambda y . s $ と書くことで二つの引数を持つ関数を表現できる。 -これは $ x $ に $ v $ が与えられた時、$ y $ を受けとり、 $ s $ の抽象内の自由な $ x $ を $ v $ に置き換えた部分を置換する関数、を返す。 -例えば $ (\lambda x . \lambda y . s) \; v \; w $ は $ (\lambda y . [x \mapsto v] s) w $ に簡約され、 $ [y \mapsto w][x \mapsto v]s $ に簡約される。 -なお、複数の引数を取る関数を高階関数に変換することはカリー化と呼ばれる。 +次に Meta DataSegment について考える。 +Meta DataSegment はプログラムに出現する DataSegment の共用体であった。 +これを DataSegment の構造体に変更する。 +こうすることにより、 Meta DataSegment はプログラム中に出現する DataSegment を必ず持つため、Meta DataSegment は任意の DataSegment の部分型となる。 +もしくは各 DataSegment の全てのフィールドを含むような1つの構造体でも良い。 +第~\ref{chapter:cbc-type}章における Meta DataSegment はそのように定義している。 +なお、GearsOSでは DataSegment の共用体をプログラムで必要な数だか持つ実装になっている。 -% TODO: ラムダの再帰とかペアとかの解説 直積直和 - -ラムダ計算の帰納的な項は以下のように定義される。 - -\begin{definition} - $ V $ を変数名の加算集合とする。項の集合は以下を満たす最小の集合 $ T $ である。 +具体的な CbC における Meta DataSegment であるContext (リスト~\ref{src:type-mds})は、 DataSegment の集合を値として持っているために明らかに DataSegment よりも多くの情報を持っている。 +\lstinputlisting[label=src:type-mds, caption=CbC の Meta DataSegment である Context] {src/type-mds.h} - \begin{eqnarray*} - 任意の x \in V について x \in T \\ - t_1 \in T かつ x in V ならば \lambda x . t \in T \\ - t_1 \in T かつ t_2 \in T ならば t_1 \; t_2 \in T - \end{eqnarray*} -\end{definition} - -また、形式的な自由変数の定義を与える。 +部分型として定義するなら以下のような定義となる。 \begin{definition} - 項 $ t $ の自由変数の集合は $ FV(t)$と書き、以下のように定義される。 + Meta DataSegment の定義 - \begin{eqnarray*} - FV(x) = \{ x \} \\ - FV(\lambda . t_1 x) = FV(t_1) \setminus \{ x \} \\ - FV(t_1 \; t_2) = FV(t_1) \cup FV(t_2) - \end{eqnarray*} + \begin{align*} + Meta \; DataSegment <: DataSegment_i^{i \in N} && \text{S-MDS} + \end{align*} \end{definition} -記号 $ \setminus $ は集合に対する二項演算子であり、$ S \setminus T := {x \in S : x \notin T}$ である。 -つまり、$ t_1 $の内部の自由変数の集合から $ x $ を抜いた集合である。 - -最後に代入について定義する。 -代入の操作は直感的には置換であるが、変数の束縛に注意しなくてはならない。 -例えば抽象への代入を以下のように定義する。 - -\begin{equation*} - [ x \mapsto s ] (\lambda y . t_1) = \lambda y . [ x \mapsto s] t_1 -\end{equation*} - -この場合、束縛変数の名前によっては定義が破綻してしまう。例えば以下のようになる。 - -\begin{equation*} - [x \mapsto y](\lambda x . x) = \lambda x . y -\end{equation*} - -$ \lambda $ よって束縛されているはずの $ x $ が書き変わっている。 -これはスコープとして振る舞っていないので誤っている。 -この問題は項 $ t $ 内の変数 $ x $ の自由な出現と束縛された出現を区別しなかったために出現した誤りである。 - -そこで、$ x $ を束縛する項に対しては置換行なわないように定義を変える。 - -\begin{itemize} - \item $ y = x $ の場合 - \begin{equation*} - [ x \mapsto s ] (\lambda y . t_1) = \lambda y . t_1 - \end{equation*} - - \item $ y \neq x $ の場合 - \begin{equation*} - [ x \mapsto s ] (\lambda y . t_1) = \lambda y . [ x \mapsto s] t_1 - \end{equation*} -\end{itemize} - -この場合は束縛された変数を上書きしないが、逆に自由変数を束縛するケースが発生する。 -具体的には以下である。 +なお、$N$はあるプログラムに出現するデータセグメントの名前の集合であるとする。 -\begin{equation*} - [ x \mapsto z] (\lambda z . x) = \lambda z . z -\end{equation*} - -項 $ s $ 中の自由変数が項 $ t $ に代入されて束縛される現象は変数捕獲と呼ばれる。 -これを避けるためには $ t $ の束縛変数の名前が $ s $ の自由変数の名前と異なることを保証する必要がある。 -変数捕獲を回避した代入操作は捕獲回避代入と呼ばれる。 -代入における名前の衝突を回避するために項の束縛変数の名前を一貫して変更することで変数捕獲を回避する方法も存在する。 -束縛変数の名前を一貫して変更することをアルファ変換と呼ばれる。 -これは関数抽象に対する束縛変数は問わないという直感からくるもので、 $ \lambda x . x $ も $ \lambda y . y $ も振舞いとしては同じ関数であるとみなすものである。 -捕獲回避の条件を追加した代入の定義は以下のような定義となる。 - -\begin{itemize} - \item 変数への代入 - - \begin{equation*} - [ x \mapsto s ] x = s - \end{equation*} - - \item 存在しない変数への代入($ y \neq x $ の時) - - \begin{equation*} - [ x \mapsto s ] y = y - \end{equation*} - - \item 抽象内の項への代入($ y \neq x $ かつ $ y $ が $ s $ の自由変数でない) - - \begin{equation*} - [ x \mapsto s ] (\lambda y . t_1) = \lambda y . [ x \mapsto s] t_1 - \end{equation*} - - \item 適用への代入 - - \begin{equation*} - [x \mapsto s] (t_1 \; t_2) = (t_1[x\mapsto s])([x \mapsto s] t_2) - \end{equation*} - -\end{itemize} - -この定義は少なくとも代入が行なわれる際には正しく代入が行なえる。 -さらに、抽象が束縛している変数を名前では無く数字として扱う名無し表現も存在する。 -これは De Brujin 表現~\cite{DEBRUIJN1972381}と呼ばれ、コンパイラ内部などでの項表現として用いられる。 - -最終的な型無しラムダ計算 $ \lambda $ の項の定義と評価の要約を示す。 +次に CodeSegment の型について考える。 +CodeSegment は DataSegment を DataSegment へと移す関数型とする。 \begin{definition} - $ \rightarrow $ (型無し) - - 項 - \begin{align*} - t ::= && \text{項} \\ - \lambda x . t && \text{ラムダ抽象} \\ - t \; t && \text{関数適用} - \end{align*} - - 値 - \begin{align*} - v ::= && \text{値} \\ - \lambda x . t && \text{ラムダ抽象値} - \end{align*} - - 評価( $ t \rightarrow t' $) + CodeSegment の定義 \begin{align*} - \AxiomC{$ t_1 \rightarrow t_1'$} - \UnaryInfC{$t_1 \; t_2 \rightarrow t_1' t_2$} - \DisplayProof && \text{E-APP1} \\ - \AxiomC{$ t_2 \rightarrow t_2'$} - \UnaryInfC{$v_1 \; t_2 \rightarrow v_1 t_2'$} - \DisplayProof && \text{E-APP2} \\ - (\lambda x . t_{12}) \; v_2 \rightarrow [ x \mapsto v_2] t_{12} - && \text{E-APPABS} + DataSegment \rightarrow DataSegment && \text{T-CS} \end{align*} \end{definition} -項は変数かラムダ抽象か関数適用の3つにより構成される。 -また、ラムダ抽象値は全て値である。 -加えて評価は関数適用を行なう E-APPABS 計算規則と、適用の項を書き換える E-APP1 と E-APP2 合同規則により定義される。 - -この定義からも評価戦略と評価順序が分かる。 -関数を適用する E-APPABS は左側が抽象であり、右側が値である $v_2$ の時にしか適用されない。 -逆に、規則 E-APP1 の$t_1$は任意の項にマッチするため関数部分が値でない関数適用に用いる。 -一方、E-APP2 は左辺が値であるようになるまで評価されない。 -よって、関数適用 $ t_1 \; t_2 $ の評価順は、まずE-APP1を用いて$t_1$が値となった後にE-APP2を用いて$t_2$を値とし、最後にE-APPABSで関数を適用を行なう。 - - -% }}} - -% {{{ 単純型付きラムダ計算 - -\section{単純型付きラムダ計算} -型無しラムダ計算に対して単純型を適用する場合、ラムダ抽象の型について考える必要がある。 -ラムダ抽象は値を取って値を返すため、関数として考えることもできる。 -差し当たりBool型における関数の型を $ \lambda x . t : \rightarrow $ と定義する。 -この定義においては $ \lambda x . true $ についても $\lambda x . \lambda y . y $ のような型も同一の型を持つ。 -この二つの項は値を適用すると値を返すという点では同じであるが、前者は $ true $ を返し、後者は $ \lambda y . y $ を返す。 -これでは関数を適用した際に返す値の型は関数の型から予測できず、加えてどの値に対して適用可能かも分からない。 -そのために引数にどのような型を期待しているのか、正しい型の値を適用するとどの型の値を返すのかを型情報に追加する。 -具体的には以下のように $ \rightarrow $ を $ T_1 \rightarrow T_2 $ の形をした無限の型の族に置き換える。 +そして Meta CodeSegmentは Meta DataSegment を Meta DataSegment へと移す関数となる。 \begin{definition} - 型 Bool 上の単純型の集合は次の文法により生成される。 + Meta CodeSegment の定義 \begin{align*} - t ::= && 型 : \\ - T \rightarrow T && 関数の型 \\ - Bool && ブール値型 + Meta \; DataSegment \rightarrow Meta \; DataSegment && \text{T-MCS} \end{align*} \end{definition} -なお、型構築子 $ \rightarrow $ は右結合である。 -つまり $ T_1 \rightarrow T_2 \rightarrow T_3 $ は$ T_1 \rightarrow (T_2 \rightarrow T_3) $となる。 - -$ \lambda x . t $ に対して型を割り当てる時、明示的に型付けする方法と暗黙的に型付けする方法がある。 -明示的に型付けを行なう場合はプログラマが項に型の注釈を記述する。 -暗黙的に型付けを行なう場合は型検査器に情報を推論させ、型を再構築させる。 -型推論は $ \lambda $ 計算の文献内では型割り当て体系と呼ぶこともある。 -今回は明示的に型を指定する方法を取る。 - -$ \lambda $ 抽象の引数の型が分かれば、結果の型は本体 $ t_2 $となる。 -ここで、$ t_2 $内における $ x $ の出現は型 $ T_1 $ の項を表すと仮定する必要がある。 -これは引数に対して正しい型の値が渡されたにも関わらず抽象内で異なる型として振る舞うのを禁止するためである。 -この $ \lambda $ 抽象の型付けは以下の T-ABS によって定義される。 - - -\begin{align*} - \AxiomC{$x : T_1 \vdash t_2 : T_2$} - \UnaryInfC{$ \vdash \lambda x : T_1 . t_2 : T_1 \rightarrow T_2$} - \DisplayProof && \text{T-ABS} -\end{align*} - -項は抽象を入れ子で持つ可能性があるため、引数の仮定は複数持ちうる。 -このため型付け関係は二項関係 $ t : T $ から、三項関係 $ \Gamma \vdash t : T $ となる。 -ここで $ \Gamma $ とは $ t $ に表われる自由変数の型の仮定の集合である。 -$ \Gamma $ は型付け文脈や型環境と呼ばれ、$ \Gamma \vdash t : T $ は「型付け文脈 $ \Gamma $ において項 $ t$ は型$T$を持つ」と読む。 -空の文脈は $ \emptyset$ と書かえることもあるが、通常は省略して $ \vdash t : T $ と書く。 -また、型環境に対する $ , $ 演算子は $ \Gamma $ の右に新しい束縛を加えて拡張する。 - -新しい束縛と既に $ \Gamma $ に表われている束縛は混同しないように名前 $ x$は$\Gamma $に存在しない名前から選ばれるものとする。 -これはアルファ変換により$\lambda$抽象の束縛名は一貫して変更ができるため、常に満たせる。 +ここで具体的なコード(リスト~\ref{src:type-cs})と比較してみる。 -ラムダ抽象に型を持たせる規則の一般的な形は - -\begin{align*} - \AxiomC{$ \Gamma, x : T_1 \vdash t_2 : T_2$} - \UnaryInfC{$ \Gamma \vdash \lambda x : T_1 . t_2 : T_1 \rightarrow T_2$} - \DisplayProof && \text{T-ABS} -\end{align*} - -であり、変数の型付け規則は - -\begin{align*} - \AxiomC{$ x : T \in \Gamma $} - \UnaryInfC{$ \Gamma \vdash x : T$} - \DisplayProof && \text{T-VAR} -\end{align*} - -である。 -$ x : T \in T $ は 、$ \Gamma$において $x$ に仮定された型は $ T $ である、と読む。 - -最後に関数適用の型付け規則を定義する。 - -\begin{align*} - \AxiomC{$ \Gamma \vdash t_1 : T_{11} \rightarrow T_{12}$} - \AxiomC{$ \Gamma \vdash t_2 : T_{11}$} - \BinaryInfC{$ \Gamma \vdash t_1 \; t_2 : T_{12}$} - \DisplayProof && \text{T-APP} -\end{align*} - -もし $t_1$ が$ T_{11}$の引数を $ T_{12}$の計算結果に移す関数へ評価され、$ t_2$が型$T_{11}$の計算結果にに評価されるのであれば、$t_1$ を $ t_2$に適用した計算結果は $ T_{12}$の型を持つ。 -ブール定数と条件式の型付け規則は型付き算術式と時と同様である。 +\lstinputlisting[label=src:type-cs, caption=具体的なCbCにおける CodeSegment] {src/type-cs.c} -最終的な純粋単純型付きラムダ計算の規則を示す。 -純粋とは基本型を持たないという意味であり、純粋単純型付きラムダ計算にはブールのような型を持たない。 -この純粋単純型付きラムダ計算でブール値を扱う場合は型環境$\Gamma$を考慮してブール値の規則を追加すれば良い。 - -\begin{definition} - $ \rightarrow $ (型付き)の構文 - - \begin{align*} - t ::= && \text{項} \\ - x && \text{変数} \\ - \lambda x : T . t && \text{ラムダ抽象} \\ - t \; t && \text{関数適用} \\ - \end{align*} - - \begin{align*} - v ::= && \text{項} \\ - \lambda x : T . t && \text{ラムダ抽象値} \\ - \end{align*} - - \begin{align*} - T ::= && \text{型} \\ - T \rightarrow T && \text{関数型} \\ - \end{align*} - - \begin{align*} - \Gamma ::= && \text{文脈} \\ - \emptyset && \text{空の文脈} \\ - \Gamma , x : T && \text{項変数の束縛} \\ - \end{align*} -\end{definition} +CodeSegment \verb/getMinHeight/ は DataSegment \verb/Allocate/ と \verb/AkashaInfo/ を引数に取っている。 +現状は \verb/Context/ も継続のために渡しているが、本来ノーマルレベルからはアクセスできないために隠れているとする。 +その場合、引数の型は $ \{ allocate : Allocate , akashaInfo : AkahsaInfo\} $ となる。 +また、返り値は構文的には存在していないが、軽量継続で渡す値は $ Context $ である。 +よって \verb/getMinHeight/ の型は $ \{ allocate : Allocate , akashaInfo : AkahsaInfo\} \rightarrow Context $ となる。 +$ Context $ の型は Meta DataSegment なので、 subtype の S-ARROW より $Context $の上位型への変更ができる。 -\begin{definition} - $ \rightarrow $ (型付き)の評価($ t \rightarrow t'$) - - \begin{align*} - \AxiomC{$t_1 \rightarrow t_1'$} - \UnaryInfC{$t_1 \; t_2 \rightarrow t_1' \; t_2$} - \DisplayProof && \text{E-APP1} \\ - \AxiomC{$t_2 \rightarrow t_2'$} - \UnaryInfC{$v_1 \; t_2 \rightarrow v_1 \; t_2'$} - \DisplayProof && \text{E-APP2} \\ - (\lambda x : T_{11} . t_{12}) v_2 \rightarrow [ x \mapsto v_2] t_{12} && - \text{E-APPABS} - \end{align*} -\end{definition} - -\begin{definition} - $ \rightarrow $ (型付き)の型付け($\Gamma \vdash t : T $) - - \begin{align*} - \AxiomC{$ x : T \in \Gamma$} - \UnaryInfC{$\Gamma \vdash x : T $} - \DisplayProof && \text{T-VAR} \\ - \AxiomC{$\Gamma , x : T_1 \vdash t_2 : T_2$} - \UnaryInfC{$\Gamma \vdash \lambda x : T_1 . t_2 : T_1 \rightarrow T_2$} - \DisplayProof && \text{E-ABS} \\ - \AxiomC{$ \Gamma \vdash t_1 : T_{11} \rightarrow T_{12}$} - \AxiomC{$ \Gamma \vdash t_2 : T_{11}$} - \BinaryInfC{$\Gamma \vdash t_1 \; t_2 : t_{12}$} - \DisplayProof && \text{T-APP} - \end{align*} -\end{definition} - -単純型付きラムダ計算の型付け規則のインスタンスも型付き算術式のように導出木をすることで示せる。 -例えば $ (\lambda x : Bool\; x) \; true $ が空の文脈において $ Bool$を持つことは以下の木で表せる。 +$ \{ allocat; : Allocate , akashaInfo : AkahsaInfo\} $ を $X$と置いて、\verb/getMinHeignt/ を $ X \rightarrow X $ とする際の導出木は以下である。 \begin{prooftree} - \AxiomC{$ x : Bool \in x : Bool$} - \RightLabel{T-VAR} - \UnaryInfC{$x : Bool \vdash x : Bool$} - \RightLabel{T-ABS} - \UnaryInfC{$\vdash \lambda x : Bool . x : Bool \rightarrow Bool$} + \AxiomC{} + \RightLabel{S-REFL} + \UnaryInfC{$ X <: X $} \AxiomC{} - \RightLabel{T-TRUE} - \UnaryInfC{$\vdash true : Bool$} - \RightLabel{T-APP} - \BinaryInfC{$\vdash (\lambda x : Bool . x) \; true : Bool $} + \RightLabel{S-MDS} + \UnaryInfC{$ Conttext <: X$} + \RightLabel{S-ARROW} + \BinaryInfC{$ X \rightarrow Context <: X \rightarrow X$} \end{prooftree} -純粋型付きラムダ計算の型システムにおいて、閉じた項に対して進行定理と保存定理は成り立つ\cite{Pierce:2002:TPL:509043}\cite{pierce2013型システム入門プログラミング言語と型の理論}。 % FIXME: 進行定理と保存定理の証明。 -閉じた項、という制限が付いているのは $ f \; true $ といった自由変数が存在する項は正規形ではあるが値でないからである。 -しかし、開いた項に関しては評価が行なえないために型システムの検査対象に含まれない。 +返り値部分を部分型として定義することにより、軽量継続先が上位型であればどの CodeSegment へと遷移しても良い。 +プログラムによっては遷移先は確定しているために部分型にする必要性は無いが、メタ計算やライブラリなどの遷移先が不定の場合は一度部分型として確定し、その後コンパイル時やランタイム時に包摂関係から具体型を導けば良い。 +例えばコンパイル時に解決すればライブラリの静的リンク時実行コード生成が行なえ、ランタイム時に解決すればネットワークを経由するプログラムとの接続初期化に利用できる。 +例えば、プロトコルがバージョンを持つ場合に接続先のプログラムが利用しているプロトコルと互換性があるかの判断を Context どうしの部分型関係で判断できる。 + +また、stub のみに注目すると、stub は Context から具体的なDataSegment X を取り出す操作に相当し、S-ARROWの左側の前提のような振舞いをする。 +加えて、軽量継続する際に X の計算結果を Context に格納してから goto する部分を別の Meta CodeSegment として分離すると、S-ARROWの右側の前提のような振舞いを行なう。 +このようにノーマルレベルの CodeSegment の先頭と末尾にメタ計算を接続することによってノーマルレベルの CodeSegment が型付けできる。 +型付けにDataSegment の集合としての Meta DataSegment が必須になるが、これは構造体として定義可能なためコンパイル時に生成することで CbC に組み込むことができる。 + +なお、メタ計算に利用する Meta DataSegment と Meta DataSegment も同様に型付けできる。 +ここで興味深いのはあるレベルの CodeSegment は同レベルの DataSegment において型付けされるが、一つ上の階層から見ると、下の階層のDataSegmentとして一貫して扱えることにある。 +このようにメタ計算を階層化することにより、メタ計算で拡張された計算に対しても他のメタ計算が容易に適用できる。 % }}} -