私は最近、HaskellとAgda(依存型関数型プログラミング言語)を取り上げた大学のコースを修了しましたが、これらのラムダ計算をコンビネータ論理に置き換えることができるかどうか疑問に思いました。 Haskellでは、これはSおよびKコンビネータを使用して可能であるように思われるため、ポイントフリーになります。アグダに相当するものは何だろうと思っていました。つまり、変数を使用せずに、依存型の関数型プログラミング言語をAgdaと同等にすることはできますか?
また、どういうわけか定量化をコンビネータに置き換えることは可能ですか?これが偶然かどうかはわかりませんが、たとえば全称記号を使用すると、型シグネチャがラムダ式のように見えます。意味を変えずに全称記号を型シグネチャから削除する方法はありますか?例えば。に:
forall a : Int -> a < 0 -> a + a < a
フォラルを使わなくても同じことが表現できますか?
それで、もう少し考えて、少し進歩しました。これは、Martin-Löfの楽しくシンプルな(しかし一貫性のない)Set : Set
システムを組み合わせスタイルでエンコードする最初の試みです。終了するのは良い方法ではありませんが、開始するのに最も簡単な場所です。この型理論の構文は、型注釈、Pi型、およびユニバースセットを含むラムダ計算です。
完全を期すために、ルールを示します。コンテキストの妥当性は、Set
sに存在する新しい変数に隣接することにより、空からコンテキストを構築できることを示しています。
G |- valid G |- S : Set
-------------- ----------------------------- x fresh for G
. |- valid G, x:S |- valid
そして今、私たちは、与えられた文脈で用語の型を合成する方法、そしてそれに含まれる用語の計算動作まで何かの型を変更する方法を言うことができます。
G |- valid G |- S : Set G |- T : Pi S \ x:S -> Set
------------------ ---------------------------------------------
G |- Set : Set G |- Pi S T : Set
G |- S : Set G, x:S |- t : T x G |- f : Pi S T G |- s : S
------------------------------------ --------------------------------
G |- \ x:S -> t : Pi S T G |- f s : T s
G |- valid G |- s : S G |- T : Set
-------------- x:S in G ----------------------------- S ={beta} T
G |- x : S G |- s : T
オリジナルとの小さなバリエーションで、ラムダを唯一のバインディング演算子にしたので、Piの2番目の引数は、戻り値の型が入力に依存する方法を計算する関数である必要があります。慣例により(たとえばAgdaでは、残念ながらHaskellではそうではありません)、ラムダのスコープは可能な限り右方向に拡張されるため、抽象化が高次演算子の最後の引数である場合は、抽象化を括弧なしのままにすることができます。それはPiです。 Agdaタイプ(x : S) -> T
はPi S \ x:S -> T
になります。
(余談。抽象化の型を合成できるようにする場合は、ラムダの型注釈が必要です。手口としてcheckingと入力しますが、そこからパーツのタイプを推測する方法がないため、(\ x -> t) s
のようなベータレデックスをチェックするための注釈が必要です。現代の設計者には、型をチェックし、ベータ版を構文から除外することをお勧めします。)
(余談。Set:Set
はさまざまな「嘘つきのパラドックス」のエンコードを許可するため、このシステムは一貫性がありません。Martin-Löfがこの理論を提案したとき、Girardは彼にエンコードを送信しました。彼自身の一貫性のないシステムUでのそれの。ハーケンズによるその後のパラドックスは、私たちが知っている最も近い有毒な構造です。)
とにかく、PiとSetの2つの追加の記号があるので、おそらくS、Kと2つの追加の記号を組み合わせた翻訳を管理するかもしれません。私は宇宙にUを、製品にPを選択しました。
これで、型なしの組み合わせ構文(自由変数を使用)を定義できます。
data SKUP = S | K | U | P deriving (Show, Eq)
data Unty a
= C SKUP
| Unty a :. Unty a
| V a
deriving (Functor, Eq)
infixl 4 :.
この構文には、タイプa
で表される自由変数を含める手段が含まれていることに注意してください。私の側の反射であることに加えて(名前に値するすべての構文は、return
埋め込み変数と>>=
実行置換を備えた無料のモナドです)、バインディングを使用して用語を変換するプロセスの中間段階を表すと便利です。それらの組み合わせの形に。
正規化は次のとおりです。
norm :: Unty a -> Unty a
norm (f :. a) = norm f $. a
norm c = c
($.) :: Unty a -> Unty a -> Unty a -- requires first arg in normal form
C S :. f :. a $. g = f $. g $. (a :. g) -- S f a g = f g (a g) share environment
C K :. a $. g = a -- K a g = a drop environment
n $. g = n :. norm g -- guarantees output in normal form
infixl 4 $.
(読者の演習は、正確に正規形のタイプを定義し、これらの操作のタイプをシャープにすることです。)
これで、型理論の構文を定義できます。
data Tm a
= Var a
| Lam (Tm a) (Tm (Su a)) -- Lam is the only place where binding happens
| Tm a :$ Tm a
| Pi (Tm a) (Tm a) -- the second arg of Pi is a function computing a Set
| Set
deriving (Show, Functor)
infixl 4 :$
data Ze
magic :: Ze -> a
magic x = x `seq` error "Tragic!"
data Su a = Ze | Su a deriving (Show, Functor, Eq)
私は、ベルガルドとフックの方法でド・ブラウン・インデックス表現を使用します(バードとパターソンによって普及したように)。タイプSu a
にはa
よりも1つ多い要素があり、バインダーの下の自由変数のタイプとして使用します。Ze
は新しくバインドされた変数として、Su x
は古い自由変数のシフト表現です。 x
。
これで、ブラケットの抽象化に基づいて通常の翻訳を取得します。
tm :: Tm a -> Unty a
tm (Var a) = V a
tm (Lam _ b) = bra (tm b)
tm (f :$ a) = tm f :. tm a
tm (Pi a b) = C P :. tm a :. tm b
tm Set = C U
bra :: Unty (Su a) -> Unty a -- binds a variable, building a function
bra (V Ze) = C S :. C K :. C K -- the variable itself yields the identity
bra (V (Su x)) = C K :. V x -- free variables become constants
bra (C c) = C K :. C c -- combinators become constant
bra (f :. a) = C S :. bra f :. bra a -- S is exactly lifted application
この翻訳は、コンビネータの使用方法を示しています。これにより、コンビネータのタイプがどうあるべきかについてかなりの手がかりが得られます。 U
とP
は単なるセットコンストラクターであるため、変換されていない型を記述し、Piの「Agda表記」を許可すると、次のようになります。
U : Set
P : (A : Set) -> (B : (a : A) -> Set) -> Set
K
コンビネータは、あるタイプA
の値を、他のタイプG
よりも定数関数に持ち上げるために使用されます。
G : Set A : Set
-------------------------------
K : (a : A) -> (g : G) -> A
S
コンビネータは、すべてのパーツが依存する可能性のあるタイプを超えてアプリケーションを持ち上げるために使用されます。
G : Set
A : (g : G) -> Set
B : (g : G) -> (a : A g) -> Set
----------------------------------------------------
S : (f : (g : G) -> (a : A g) -> B g a ) ->
(a : (g : G) -> A g ) ->
(g : G) -> B g (a g)
S
の型を見ると、型理論のcontextualizedアプリケーションルールが正確に記述されていることがわかります。そのため、アプリケーション構造を反映するのに適しています。それがその仕事です!
その後、閉じたものにのみアプリケーションがあります
f : Pi A B
a : A
--------------
f a : B a
しかし、問題があります。コンビネータの型は、コンビネータ型理論ではなく、通常の型理論で書いています。幸いなことに、私は翻訳を行うマシンを持っています。
---------
U : U
---------------------------------------------------------
P : PU(S(S(KP)(S(S(KP)(SKK))(S(KK)(KU))))(S(KK)(KU)))
G : U
A : U
-----------------------------------------
K : P[A](S(S(KP)(K[G]))(S(KK)(K[A])))
G : U
A : P[G](KU)
B : P[G](S(S(KP)(S(K[A])(SKK)))(S(KK)(KU)))
--------------------------------------------------------------------------------------
S : P(P[G](S(S(KP)(S(K[A])(SKK)))(S(S(KS)(S(S(KS)(S(KK)(K[B])))(S(KK)(SKK))))
(S(S(KS)(KK))(KK)))))(S(S(KP)(S(S(KP)(K[G]))(S(S(KS)(S(KK)(K[A])))
(S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KP)))(S(KK)(K[G]))))
(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(S(KS)(S(KK)(KS)))
(S(S(KS)(S(KK)(KK)))(S(KK)(K[B])))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))
(S(KK)(KK))))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(KK)(KK)))
(S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))(S(KK)(KK)))))))
M : A B : U
----------------- A ={norm} B
M : B
これで、読めない栄光の中で、Set:Set
の組み合わせプレゼンテーションができました!
まだ少し問題があります。システムの構文では、用語から、G
および同様にA
のB
、S
、およびK
パラメーターを推測する方法はありません。これに対応して、タイピング派生をアルゴリズムで検証できますが、元のシステムのようにコンビネータ項をタイプチェックすることはできません。うまくいくかもしれないのは、SとKの使用に関するタイプ注釈を付けるためにタイプチェッカーへの入力を要求し、派生を効果的に記録することです。しかし、それはワームの別の缶です...
開始するのに十分な熱意がある場合、これは停止するのに適した場所です。残りは「舞台裏」のものです。
関連する型理論用語からの括弧抽象化変換を使用して、これらの組み合わせ型を生成しました。私がそれをどのように行ったかを示し、この投稿を完全に無意味ではないようにするために、私の機器を提供させてください。
次のように、パラメーターを完全に抽象化したコンビネーターのタイプを記述できます。便利なpil
関数を使用します。この関数は、Piとラムダを組み合わせてドメインタイプの繰り返しを回避し、Haskellの関数空間を使用して変数をバインドできるようにします。おそらく、あなたはほとんど以下を読むことができます!
pTy :: Tm a
pTy = fmap magic $
pil Set $ \ _A -> pil (pil _A $ \ _ -> Set) $ \ _B -> Set
kTy :: Tm a
kTy = fmap magic $
pil Set $ \ _G -> pil Set $ \ _A -> pil _A $ \ a -> pil _G $ \ g -> _A
sTy :: Tm a
sTy = fmap magic $
pil Set $ \ _G ->
pil (pil _G $ \ g -> Set) $ \ _A ->
pil (pil _G $ \ g -> pil (_A :$ g) $ \ _ -> Set) $ \ _B ->
pil (pil _G $ \ g -> pil (_A :$ g) $ \ a -> _B :$ g :$ a) $ \ f ->
pil (pil _G $ \ g -> _A :$ g) $ \ a ->
pil _G $ \ g -> _B :$ g :$ (a :$ g)
これらを定義して、関連するopenサブタームを抽出し、翻訳を実行しました。
pil
を作成する方法は次のとおりです。まず、変数に使用されるFin
iteセットのクラスを定義します。このようなすべてのセットには、コンストラクターを保持する上記のセットへのemb
eddingと、新しいtop
要素があり、それらを区別できます。embd
関数は、値がemb
のイメージにあるかどうかを示します。
class Fin x where
top :: Su x
emb :: x -> Su x
embd :: Su x -> Maybe x
もちろん、Fin
とZe
のSuc
をインスタンス化することもできます。
instance Fin Ze where
top = Ze -- Ze is the only, so the highest
emb = magic
embd _ = Nothing -- there was nothing to embed
instance Fin x => Fin (Su x) where
top = Su top -- the highest is one higher
emb Ze = Ze -- emb preserves Ze
emb (Su x) = Su (emb x) -- and Su
embd Ze = Just Ze -- Ze is definitely embedded
embd (Su x) = fmap Su (embd x) -- otherwise, wait and see
これで、weakening演算を使用して、以下を定義できます。
class (Fin x, Fin y) => Le x y where
wk :: x -> y
wk
関数は、x
の要素をy
の最大要素として埋め込む必要があります。これにより、y
の余分なものが小さくなり、したがってdeBruijnインデックス用語でよりローカルにバインドされます。
instance Fin y => Le Ze y where
wk = magic -- nothing to embed
instance Le x y => Le (Su x) (Su y) where
wk x = case embd x of
Nothing -> top -- top maps to top
Just y -> emb (wk y) -- embedded gets weakened and embedded
そして、それを整理したら、ランクNのスカルダガリーが残りを行います。
lam :: forall x. Tm x -> ((forall y. Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
lam s f = Lam s (f (Var (wk (Ze :: Su x))))
pil :: forall x. Tm x -> ((forall y . Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
pil s f = Pi s (lam s f)
高階関数は、変数を表す項を提供するだけでなく、overloadedを提供します。これは、変数が存在するすべてのスコープで変数の正しい表現になります。目に見える。つまり、異なるスコープをタイプで区別するのに苦労しているという事実は、HaskellタイプチェッカーにdeBruijn表現への変換に必要なシフトを計算するのに十分な情報を提供します。なぜ犬を飼って自分で吠えるのですか?
「ブラケットの抽象化」は、状況によっては依存型でも機能すると思います。次の論文のセクション5には、いくつかのKタイプとSタイプがあります。
とんでもないが意味のある偶然
依存型セーフ構文と評価
Conor McBride、ストラスクライド大学、2010年
ラムダ式を組み合わせ式に変換することは、自然演繹証明をヒルベルトスタイルの証明に変換することにほぼ対応します。