web-dev-qa-db-ja.com

タイプレベルのプログラミングの例は何ですか?

「タイプレベルのプログラミング」の意味がわかりません。また、Googleを使用して適切な説明を見つけることもできません。

誰かが型レベルのプログラミングを示す例を提供してもらえますか?パラダイムの説明および/または定義は有用であり、高く評価されます。

29
Vanio Begic

ほとんどの静的に型付けされた言語には、値レベルと型レベルの2つの「ドメイン」があります(一部の言語にはさらに多くのドメインがあります)。型レベルのプログラミングには、コンパイル時に評価される型システムでのエンコーディングロジック(多くの場合、関数の抽象化)が含まれます。いくつかの例は、テンプレートメタプログラミングまたはHaskellタイプファミリーです。

Haskellでこの例を実行するには、いくつかの言語拡張が必要ですが、今のところそれらを無視して、型族を関数であるが型レベルの数値(Nat)であると見なします。

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

import GHC.TypeLits
import Data.Proxy

-- value-level
odd :: Integer -> Bool
odd 0 = False
odd 1 = True
odd n = odd (n-2)

-- type-level
type family Odd (n :: Nat) :: Bool where
    Odd 0 = False
    Odd 1 = True
    Odd n = Odd (n - 2)

test1 = Proxy :: Proxy (Odd 10)
test2 = Proxy :: Proxy (Odd 11)

ここでは、自然数valueが奇数かどうかをテストする代わりに、自然数typeが奇数かどうかをテストし、タイプレベルのブール値に減らしています。コンパイル時。このプログラムを評価すると、test1およびtest2のタイプはコンパイル時に次のように計算されます。

λ: :type test1
test1 :: Proxy 'False
λ: :type test2
test2 :: Proxy 'True

これが型レベルのプログラミングの本質です。言語によっては、さまざまな用途を持つ型レベルで複雑なロジックをエンコードできる場合があります。たとえば、値レベルで特定の動作を制限したり、リソースのファイナライズを管理したり、データ構造に関する詳細情報を保存したりします。

23
Stephen Diehl

42 :: Int'a' :: Charなどの値を操作する「値レベル」のプログラミングについてはすでにご存知でしょう。 Haskell、Scala、その他多くの言語では、型レベルのプログラミングにより、Int :: *Char :: *などの型を操作できます。ここで、*種類です。 具象型(Maybe aまたは[a]は具象型ですが、種類が[]Maybeまたは* -> *ではありません)。

この関数を検討してください

foo :: Char -> Int
foo x = fromEnum x

ここで、footypeCharvalueを取り、の新しい値を返します。 IntEnumインスタンスを使用してCharと入力します。この関数は値を操作します。

次に、fooを、TypeFamilies言語拡張で有効にしたこのタイプファミリーと比較します。

type family Foo (x :: *)
type instance Foo Char = Int

ここで、Footypeofkind*を取り、単純なマッピング*を使用した新しいタイプのChar -> Int。これは、型を操作する型レベルの関数です。

これは非常に単純な例であり、これがどのように役立つのか疑問に思われるかもしれません。より強力な言語ツールを使用して、コードの正しさの証明を型レベルでエンコードし始めることができます(これについて詳しくは、 Curry-Howard 対応を参照してください)。

実用的な例は、タイプレベルのプログラミングを使用して、ツリーの不変条件が保持されることを静的に保証する赤黒木です。

赤黒木には、次の単純なプロパティがあります。

  1. ノードは赤または黒のいずれかです。
  2. 根は黒です。
  3. すべての葉は黒です。 (すべての葉は根と同じ色です。)
  4. すべての赤いノードには、2つの黒い子ノードが必要です。特定のノードからその子孫リーフへのすべてのパスには、同じ数の黒いノードが含まれています。

非常に強力な型レベルのプログラミングの組み合わせであるDataKindsGADTsを使用します。

{-# LANGUAGE DataKinds, GADTS, KindSignatures #-}

import GHC.TypeLits

まず、色を表すいくつかのタイプ。

data Colour = Red | Black -- promoted to types via DataKinds

これは、ColourRedの2つのタイプが存在する新しい種類Blackを定義します。これらのタイプにはvalues(ボトムスを無視)はありませんが、とにかくそれらは必要ないことに注意してください。

赤黒木ノードは、次のGADTで表されます。

-- 'c' is the Colour of the node, either Red or Black
-- 'n' is the number of black child nodes, a type level Natural number
-- 'a' is the type of the values this node contains
data Node (c :: Colour) (n :: Nat) a where
    -- all leaves are black
    Leaf :: Node Black 1 a
    -- black nodes can have children of either colour
    B    :: Node l     n a -> a -> Node r     n a -> Node Black (n + 1) a
    -- red nodes can only have black children
    R    :: Node Black n a -> a -> Node Black n a -> Node Red n a

GADTを使用すると、ColourおよびRコンストラクターのBを型で直接表現できます。

木の根はこんな感じ

data RedBlackTree a where
    RBTree :: Node Black n a -> RedBlackTree a

現在、上記の4つのプロパティのいずれかに違反する適切に型指定されたRedBlackTreeを作成することは不可能です。

  1. 最初の制約は明らかに真であり、Colourに存在するタイプは2つだけです。
  2. RedBlackTreeの定義から、ルートは黒です。
  3. Leafコンストラクターの定義から、すべての葉は黒です。
  4. Rコンストラクターの定義から、その子は両方ともBlackノードである必要があります。同様に、各サブツリーの黒い子ノードの数は同じです(同じnが左右のサブツリーのタイプで使用されます)

これらの条件はすべて、コンパイル時にGHCによってチェックされます。つまり、赤黒木に関する仮定を無効にする不正なコードからランタイム例外が発生することはありません。重要なのは、これらの追加の利点に関連するランタイムコストがなく、すべての作業がコンパイル時に行われることです。

30
cdk

他の答えはとてもいいですが、私は一つの点を強調したいと思います。 termsのプログラミング言語理論は、ラムダ計算に強く基づいています。 「純粋な」LISPは、(多かれ少なかれ)糖度の高い型なしラムダ計算に対応します。プログラムの意味は、プログラムの実行時にラムダ計算の項がどのように削減されるかを示す評価ルールによって定義されます。

型付き言語では、用語に型を割り当てます。すべての評価ルールには、評価によってタイプがどのように保持されるかを示す対応するタイプルールがあります。型システムに応じて、型が互いにどのように関連するかを定義する他のルールもあります。十分に興味深い型システムを取得すると、typesとそのルールシステムもラムダ計算の変形に対応することがわかります。

現在、ラムダ計算をプログラミング言語と考えるのが一般的ですが、元々は論理システムとして設計されていました。これが、プログラミング言語の用語の種類について推論するのに役立つ理由です。しかし、ラムダ計算のプログラミング言語の側面により、型チェッカーによって評価されるプログラムを書くことができます。

「型レベルのプログラミング」が「用語レベルのプログラミング」と実質的に異なるものではないことがわかるといいのですが、型システムに十分に強力な言語を使用することは今ではあまり一般的ではありません。その中にプログラムを書く理由。

13
Levi Pearson