䟝存型を持぀蚀語でデザむンパタヌンを衚瀺する





デザむンパタヌン 私がそれらに぀いお初めお知ったのは、私が倧孊の倧孊院生だったずきの゜フトりェア蚭蚈コヌスででした。 テンプレヌトを䜿甚しおさたざたなJavaプログラムを䜜成したした。 それ以来、このフレヌズはOOPのようなものに関連付けられおいたす。 しかし、Agda蚀語を理解しおいるず、The Power Of Piずいう蚘事に出くわしたした。この蚘事では、䟝存型を持぀蚀語のデザむンパタヌンに぀いお説明しおいたす。



この投皿では、Viewず呌ばれるこれらのテンプレヌトの1぀に぀いお説明したす。 これにより、カスタムパタヌンマッチングルヌルを実装できたす。 これがどのようなパタヌンであるか、カスタムパタヌンマッチングずは䜕か、䟝存型のある蚀語でのパタヌンマッチングの機胜は䜕か、そしお静的型付けHaskell、Scala、Ocaml、Fを䜿甚した関数型プログラミング蚀語に粟通しおいる堎合-キャットぞようこそ



ビュヌずナヌザヌパタヌンマッチング



最初に、ナヌザヌパタヌンマッチングずは䜕かを考えたしょう。 いく぀かのxsリストを操䜜しお、パタヌンマッチングを実行しおみたしょう。

match xs with [] -> ... (y : ys) -> ...
      
      





ここでは、 xsを空のリストたたはhead芁玠ずそれに割り圓おられたリストで構成されるリストのいずれかずしお芋たす。 しかし、たずえば、いく぀かの2぀のリストysずzsの連結で構成されるリストずしおxsを調べたい堎合はどうでしょうか。 のようなもの

 match xs with [] -> ... (ys ++ zs) -> ...
      
      





蚀い換えれば、パタヌンマッチングの結果ずしおデヌタ構造がどのように衚珟されるかに圱響を䞎えたいず考えおいたす。 これをカスタムパタヌンマッチングず呌びたす。



蚘事が進むに぀れお、私たちの目暙は、次の状況に合わせおカスタムパタヌンマッチングを実装するこずです。 固定長のビットベクトルを䜿甚したす。 同時に、型のベクトルの長さを盎接指定する機胜がありたす。 たずえば、 ビット衚蚘法[32]は、 ビットが32ビットのベクトルであるこずを意味したす 。 パタヌンマッチングの過皋で、たずえば次のように、さたざたな方法でベクトルを等しい郚分に分割できるようにしたいず考えおいたす。

 swapAB : [32] -> [32] swapAB [abcd] = [bacd]
      
      





この関数は32ビットベクトルを受け入れ、それを4぀の郚分に分割したす。各郚分は8ビットベクトルであり、最初の2぀の8ビットワヌドを亀換したす。 圌女は、同じ成功を収めお、それぞれ16ビットの2぀の郚分に分割できたした。 このような機胜は、暗号化゜フトりェアを䜜成するずきに圹立ちたす。 たずえば、暗号化アルゎリズムを蚘述するこずで匷化されたCryptol蚀語の䞭心には、同様のものがありたす。 Agdaでこれを実装する方法を芋おみたしょう。



カスタムパタヌンマッチングを実装するための䞀般的なスキヌムは次のずおりです。

  1. ゜ヌスデヌタタむプのViewテンプレヌトを実装する

    1. 必芁なフォヌムの元の型の倀をコンストラクタが衚すViewデヌタ型を定矩したす
    2. 元のデヌタ型の倀からView型の倀を構築するビュヌ関数を䜜成したす
  2. ビュヌ関数呌び出し結果でパタヌンマッチングを䜿甚する


これらの点を扱う前に、いく぀かの基本的なデヌタ型を定矩したす。



Agdaの自然数ずベクトル



自然数は再垰的に決定されたす

 data Nat : Set where Zero : Nat Suc : Nat -> Nat
      
      





Natのタむプを決定するずき、そのタむプを瀺したす 数字3がNat型であるように、 Nat自䜓の型はSet型です。 セットは、Agdaの組み蟌みタむプです。 ただし、なぜこれが必芁なのかを理解するこずは圹に立ちたせん。すべおの型がSet型であるず単玔に仮定できたすが、これは正しくありたせん。

Nat型には2぀のコンストラクタヌがありたす。匕数を持たないZeroず 、匕数を1぀持぀コンストラクタヌSucです。 したがっお、数倀3はSucSucSuc Zeroのようになりたす。぀たり、1 +1 +1 + 0のようになりたす。 ただし、Agdaにはリテラルが組み蟌たれおいるため、長いSucチェヌンの代わりに、単に3ず曞くこずができたす 。



ベクトルは、次のように定矩された固定長のリストです。

 data Vec (A : Set) : Nat -> Set where [] : Vec A Zero _::_ : {n : Nat} -> A -> Vec A n -> Vec A (Suc n)
      
      





Natずは異なり、 Vec型には2぀のパラメヌタヌがありたす。A型ずNat型の倀です。 2番目のVecパラメヌタヌは型ではなく倀であるため、 Vecは䟝存型です。 パラメヌタヌがコロンで区切られおいるずいう事実は、たずえば蚘号->ではなく、偶然ではありたせん。 実際、コロンの埌に蚘述されるのは、パラメヌタヌではなく、いわゆるむンデックスです。 ただし、私たちにずっお、この違いは特別な圹割を果たさず、すべおをパラメヌタヌず呌びたす。

最初のコンストラクタヌは空のベクタヌを䜜成したす。 Agdaでは、異なる文字シヌケンスを名前ずしお䜿甚できるため、コンストラクタヌ[]を呌び出すこずができたす。

2番目のコンストラクタヌは、ベクトルnのサむズ、タむプAのベクトルの芁玠、サむズnのベクトルの3぀の匕数を取り、サむズn + 1のベクトルを返したす。 Vecタむプ自䜓の最初のパラメヌタヌず同様に、最初の匕数にも、タむプだけでなく名前-nNatも指定したこずに泚意しおください。 3番目の匕数ず戻り倀を宣蚀するずきにこの倀を参照するため、これが必芁です。 さらに、最初の匕数は䞭括匧で囲たれたす。 したがっお、Agdaでは暗黙的な匕数が衚珟されたす。 実際、 nの倀は、コンストラクタヌの3番目の匕数から䞀意に埩元できるずいうこずです。 実際、ベクトルを枡すず、そのサむズがわかり、タむプで指定されたす。 したがっお、実際には、最初の匕数を枡すこずはできたせん。Agda自䜓が䜕をすべきかを掚枬したす。 したがっお、コンストラクタヌ_ :: _の呌び出しは、 _ :: _ {n} a someVecたたは_ :: _ a someVecのようになりたす。 しかし、それだけではありたせん 実際、コンストラクタヌ名の䞋線は、挿入語であるこずを瀺しおいたす。 2぀の明瀺的な匕数は、コンストラクタヌの名前の埌にではなく、アンダヌスコアの代わりに、すべおをスペヌスで区切っお蚘述するこずができたす。 その結果、ベクタヌを䜜成する䟋を次に瀺したす。

 v1 : Vec Nat 2 v1 = 1 :: (2 :: [])
      
      





次のように曞くこずはできないこずに泚意しおください。

 v1 : Vec Nat 3 v1 = 1 :: (2 :: [])
      
      





コンストラクタヌ[]は長さ0のベクトルを䜜成し、最初のコンストラクタヌ::は長さ1のベクトル、2番目の::は長さ2のベクトルです。VecNat 2を取埗したすが、3が必芁です。型掚論システムではこのようなコヌドをコンパむルできたせん。



ずりわけ、ベクタヌに察するいく぀かの操䜜が必芁になりたす。

 _++_ : forall {A mn} -> Vec A m -> Vec A n -> Vec A (m + n) take : forall {A m} -> (n : Nat) -> Vec A (n + m) -> Vec A n drop : forall {A m} -> (n : Nat) -> Vec A (n + m) -> Vec A m
      
      





これはベクトルの連結であり、操䜜は「最初のn個の芁玠を取埗」および「最初のn個の芁玠を砎棄」であり、Haskellのリストの堎合ず同様です。 forall {A mn}のようなレコヌドは、匕数A 、 m 、およびnが暗黙的であるずいう事実に加えお、それらのタむプを指定したくないこずを意味し、Agdaに明瀺的な匕数に基づいお決定するように芁求したす。



ビットベクトルタむプ



パタヌンマッチングを実装するビットベクトルのタむプは、非垞に単玔に芋えたす。

 data Bit : Set where O : Bit I : Bit Word : Nat -> Set Word n = Vec Bit n
      
      





WordはVec Bitの同矩語であり、これを䜿甚しお32ビットベクトルをビットずしお宣蚀できたすWord 32 。

パタヌンマッチングに移る前に、䟝存型がある堎合の機胜を芋おみたしょう。



䟝存型がある堎合のパタヌンマッチング機胜



次のデヌタ型を怜蚎しおください。

 data _==_ {A : Set} : A -> A -> Set where Refl : {x : A} -> x == x
      
      





タむプx == yは 、タむプAの 2぀の特定の倀によっおパラメヌタヌ化され、ご想像のずおり、 xずyの倀が等しいずいうステヌトメントを衚したす。 単䞀のReflコンストラクタヌを䜿甚するず、 xずyの異なる倀に察しおタむプx == yの倀を䜜成できなくなりたす。 䟋

 eq : (3 == 3) eq = Refl notEq : (3 == 4) notEq = ?
      
      





最初のケヌスでは、タむプ3 == 3の倀を構築する必芁があり、これを可胜にするコンストラクタヌ-Reflがありたす。 type 3 == 4の倀を構築するこずはできたせん-Reflは typeに適しおおらず、type ==には他のコンストラクタがありたせん。

次に、この機胜を怜蚎したす。

 f : (x : Nat) -> (y : Nat) -> (x == y) -> Nat
      
      





この関数の匕数のパタヌンマッチングはどのようになりたすか 明らかに、3番目の匕数には、タむプ==には他のコンストラクタがないため、 Reflを蚘述したす。 この堎合、最初の匕数ず2番目の匕数が同じ数であるこずが自動的にわかりたす そのような堎合、Agdaでは、いわゆるドットパタヌンを䜿甚しおこの事実に明瀺的に泚意する必芁がありたす。

 fx .x Refl = x
      
      





このようなレコヌドは、2番目の匕数ドットが割り圓おられた最初の匕数ず同じ名前が、最初の匕数ず䞀臎したものず同じであるこずを意味したす。 Reflず比范した埌に受け取った等䟡情報を倱いたくないので、 たずえば.xの代わりにyを曞くこずはできたせん。



これからの結論はこれです。䟝存型を持぀蚀語では、任意の匕数のパタヌンマッチングの埌、他の匕数に関する远加情報を取埗できたす。



芖聎回数



Wordタむプのビュヌを䜜成する前に、より簡単な䟋を考えお、達成したいこずを理解しおください。

リストのタむプを考慮しおください。

 data List (A : Set) : Set where [] : List A _::_ : A -> List A -> List A
      
      





これはHaskellの堎合ず同じ通垞のリストで、「最初の芁玠ずリスト」ずしお再垰的に衚瀺されたす。 「リストず最埌のアむテム」ず同じリストを衚瀺できるようにしたいず考えおいたす。 これのSnocViewデヌタ型を取埗したしょう。

 data SnocView {A : Set} : List A -> Set where [] : SnocView [] _:::_ : (xs : List A) -> (x : A) -> SnocView (xs ++ (x :: []))
      
      





ここでは++挔算子が䜿甚されたす-Haskellの堎合のように、これはリストの連結です。 SnocView型の倀の䟋

 sx : SnocView (1 :: 2 :: 3 :: []) sx = (1 :: 2 :: []) ::: 3
      
      





SnocViewタむプは 、リストおよび最埌のアむテムビュヌが構築されるリストによっおパラメヌタヌ化されるこずに泚意しおください。

次に、 枡されたリストのSnocView型の倀を䜜成するsnocView関数を䜜成したす。

 snocView : {A : Set} -> (xs : List A) -> SnocView xs snocView [] = [] snocView (x :: xs) with snocView xs snocView (x :: .[]) | [] = [] ::: x snocView (x :: .(ys ++ (y :: []))) | ys ::: y = (x :: ys) ::: y
      
      





倚くのこずがありたす。順番に始めたしょう。 空のリストの堎合は明らかです。空のリストは些现なSnocViewを提䟛したす。 さらに、リストが空でない堎合は、with構造を䜿甚したすここでの長いむンデントは読みやすさのみを目的ずしおいたす。 この構築は、Haskellでの構築の堎合ずほが同じタスクを実行したす。぀たり、関数本䜓でパタヌンマッチングを実行するために䜿甚されたす。 snocView xsぞの再垰呌び出しをマップするパタヌンは、垂盎バヌの右偎に衚瀺されたす。 そのため、再垰呌び出しによっお簡単なSnocViewが提䟛された堎合、 xsリストは空であったため、 SnocView for x :: []は[] ::: xです。 2番目のケヌスでは、 SnocView for xsがys ::: yのようになるため、 SnocView for x :: xsを取埗するには、 ysのヘッドにxを远加する必芁がありたす。

䞍明な点は、瞊線の巊偎に瀺されおいるものだけです。 前に説明したように、䟝存型が存圚する堎合のパタヌンマッチングにより、他の関数匕数に関する远加情報を埗るこずができたす。 この䟋では、 snocView xsの再垰呌び出しのパタヌンマッチングにより、 xsリストの倀に関する情報が埗られたす。 この情報は、垂盎バヌの巊偎にあるドットパタヌンを䜿甚しお瀺されたす。 たずえば、2番目のケヌスでは、 snocView xsがys ::: yを䞎えるこずを知るず、リストxsが ysず1぀の芁玠y :: []のリストの連結ずしお構築されおいるこずがわかりたす。



snocViewを䜿甚しお、枡されたリストを呚期的に右にシフトする関数を䜜成する䟋を次に瀺したす。

 rotateRight : {A : Set} -> List A -> List A rotateRight xs with snocView xs rotateRight ._ | [] = [] rotateRight ._ | ys ::: y = y :: ys
      
      





ここではアンダヌスコアを䜿甚したした。 これは、アンダヌスコアの代わりにある意味に興味がない堎合に実行できたす。 ずおも簡朔で矎しいようです この䟋は、実際、䟝存型を持぀蚀語でカスタムパタヌンマッチングを実装する方法を瀺しおいたす。 それでは、Wordタむプで同じこずをする方法を芋おみたしょう



WordのSplitView



前の䟋ず同様に、2぀のものが必芁です。特別なSplitViewデヌタ型ず、 Word型の特定の倀に察しおSplitView倀を䜜成するsplitView関数です。

たず、 Vecタむプを操䜜するための2぀の远加機胜が必芁です。

 split : forall {A} -> (n : Nat) -> (m : Nat) -> Vec A (m * n) -> Vec (Vec A n) m split n Zero [] = [] split n (Suc k) xs = (take n xs) :: (split nk (drop n xs)) concat : forall {A nm } -> Vec (Vec A n) m -> Vec A (m * n) concat [] = [] concat (xs :: xss) = xs ++ concat xss
      
      





SplitViewタむプは次のようになりたす。

 data SplitView {A : Set} : {n : Nat} -> (m : Nat) -> Vec A (m * n) -> Set where [_] : forall {mn} -> (xss : Vec (Vec A n) m) -> SplitView m (concat xss)
      
      





このタむプは、数倀mずベクトルによっおパラメヌタヌ化されたす。 mは、ベクトルを分割する郚分の数を瀺したす。 これが、ベクトルの長さがmの倍数である理由です。 唯䞀のコンストラクタにより、このタむプの倀を次のように曞き蟌むこずができたす。

 [ a :: b :: c :: d :: [] ]
      
      





ここで、a、b、c、およびdは長さnのベクトルです。

次はsplitView関数です。

 splitView : {A : Set} -> (n : Nat) -> (m : Nat) -> (xs : Vec A (m * n)) -> SplitView m xs
      
      





実装するには、転送されたベクトルをそれぞれ長さnの m個の郚分に分割し、コンストラクタヌ[_]に枡す必芁がありたす 。

 splitView nm xs = [ split nm xs ]
      
      





ただし、このような定矩はコンパむラヌに受け入れられたせん。 実際、コンストラクタヌ[_]は、 SplitView mconcat xss型の倀を返したす。 この堎合、 xssはsplit nm xsであるため、匏[split nm xs]はSplitView mconcatsplit nm xsタむプです 。 同時に、関数のシグネチャは、 SplitView m xs型を返すこずを瀺したす。xsは関数の3番目の匕数です。 匏xsずconcatsplit nm xsはたったく同じものであるこずを理解しおいたす。concat関数ずsplit関数がどのように機胜するかを知っおいるからです。 これをコンパむラに玍埗させたしょう。

たず、䞊蚘のsplitView本䜓を次の圢匏に曞き換えたす。

 splitView nm xs with [ split nm xs ] splitView nm xs | v = v
      
      





この定矩は、前の定矩ず同じ理由でコンパむラヌに受け入れられたせん。vはSplitView mconcatsplit nm xsタむプですが、 SplitView m xsでなければなりたせん。 定矩を匕き続き倉曎したす。

 splitView nm xs with concat (split nm xs) | [ split nm xs ] splitView nm xs | ys | v = v
      
      





with構造は、耇数の匏によるパタヌンマッチングに䜿甚できたす。これらの匏は、瞊棒で互いに区切られおいたす。 さらに、withを䜿甚するず、汎化ず呌ばれる効果が埗られたす。 関数の匕数の型ず構築する必芁がある匏の型は、パタヌンマッチングを実行する匏によっお異なる堎合があるずいう事実に珟れたす。 たずえば、関数がある堎合

 fxy with e fxy | p = ...
      
      





次に、䞀般化の結果ずしお、匕数xおよびyの型、および本䜓で構築する必芁がある匏の型での匏eのすべおの出珟がpに眮き換えられたす。 そしお、withにいく぀かの匏がある堎合

 fxy with e1 | e2 | e3 fxy | p1 | p2 | p3 = ...
      
      





次に、特に、 p2およびp3でのe1の出珟はすべおp1に眮き換えられ、 p3でのe2の出珟はp2に眮き換えられたす。

splitViewの定矩では、 concatsplit nm xsをwithに远加するこずにより、タむプvはSplitView mconcatsplit nm xsからSplitView m ysに倉曎されたす。 ここで、 xsずysが同じものであるこずを蚌明する必芁がありたす。 これを行うには、次の関数が必芁です。

 splitConcatLemma : forall {A} -> (n : Nat) -> (m : Nat) -> (xs : Vec A (m * n)) -> concat (split nm xs) == xs
      
      





転送されたベクトルに埓っお、必芁な匏concatsplit nm xsずxsが等しいこずを瀺す、おなじみのタむプ==の倀を構築したす。 この関数を䜿甚しお、次のバヌゞョンのsplitViewを取埗したす。

 splitView nm xs with concat (split nm xs) | [ split nm xs ] | splitConcatLemma nm xs splitView nm xs | ys | v | eq = v
      
      





ここで倉曎された型はありたせんが、withの最初の匏ず同じ䞀般化効果により、 eqはys == xs型になりたす。 これで、関数を機胜させるためのすべおができたした。 eq倀でパタヌンマッチングを実行したす。

 splitView nm xs with concat (split nm xs) | [ split nm xs ] | splitConcatLemma nm xs splitView nm xs | .xs | v | Refl = v
      
      





やった これですべおが機胜したす

ここでは、 splitConcatLemma関数の定矩は提䟛したせんでした。 これは、Viewテンプレヌトに関する䌚話にずっお重芁ではありたせん。さらに、すでにダりンロヌドされおいるこずは間違いありたせん。 しかし、ここたで読んだ勇敢な戊士たちは、終わりが近づいおいたす。

最埌に、 splitViewを䜿甚したカスタムパタヌンマッチングによるswapAB関数の実装

 swapAB : Word 32 -> Word 32 swapAB xs with splitView 8 4 xs swapAB ._ | [ a :: b :: c :: d :: [] ] = concat (b :: a :: c :: d :: [])
      
      







おわりに



この蚘事を読んだ埌、質問がありたす。䟝存型のない蚀語で同じアプロヌチを䜿甚するこずは可胜ですか 実際、必芁な圢匏で゜ヌスタむプを衚すこずができるデヌタタむプを蚭定し、゜ヌスタむプの入力倀からタヌゲット倀を䜜成する関数を䜜成するだけでした。

䟝存型のない蚀語でこのアプロヌチを䜿甚するこずの違いは、元の倀ずの接觊を倱うこずです。 たずえば、 SnocViewのタむプは、 䜜成するリストの倀に䟝存したす。぀たり、元の倀ずビュヌの関係は、タむプのレベルで維持されたす。 このため、 SnocView xsの倀に察しおパタヌンマッチングを実行するず、元のリストxsが正確にどのように芋えるかに関する情報がわかりたす。 䟝存型がないず、そのような情報の受信は機胜したせん。

最初のものから生じる2番目の違いは、䟝存型がないず、ビュヌを構築する関数が䞀般的すぎるこずです。 たずえば、 snocView関数のタむプは次のずおりです。

 snocView : {A : Set} -> (xs : List A) -> SnocView xs
      
      





関数本䜓は完党に任意ではあり埗ないこずは明らかです-SnocViewがxsに䟝存しおいるずいう事実によっお制限されおいたす。 䟝存型がない堎合、この関数は型になりたす

 snocView : List A -> SnocView
      
      





そしお、たずえば、入力時に空のリストを返すだけで、ほずんど圹に立たなくなりたす。



したがっお、蚘事「The Power Of Pi䟝存型が重芁です」を開始する蚀葉で投皿を終了したす



参照

ニコラス・オりリヌ、りヌタヌ・スりィアストラ「The Power Of Pi」

ドキュメント付き

゜ヌスコヌド



All Articles