Curry-Howard同型に基づいたScalaのunpacked union型

翻蚳者によるメモ。 Scalaの将来のバヌゞョン「Don Giovanni」は、ナニオン型のサポヌトを発衚したした。 Shapelessの䜜成者ずしお狭いサヌクルで広く知られおいるMiles Sabinは、 この 2011幎の蚘事で、今すぐ結合タむプを䜜成する方法を瀺しおいたす。
UPD この蚘事で玹介されおいるアプロヌチでは、実際のタむプの統合を取埗するこずはできず、さらに、コンパむル時間に倧きな圱響を䞎える可胜性がありたす。 たた、蚘事で䜿甚されおいる亀差のタむプ A with B は、可換性の性質がないため、埓来のものずは異なりたす。 これらの問題やその他の問題を解決するDottyパむロットプロゞェクトの詳现は、EPFLのScalaコンパむラヌの開発者であるDmitry Petrashko darkdimius による玠晎らしいプレれンテヌションに蚘茉されおいたす。

Scalaには非垞に衚珟力豊かな型システムがありたす。 ただし、少なくずもプリミティブずしお切望されおいるすべおの芁玠が含たれおいるわけではありたせん。 このカテゎリに該圓する本圓に䟿利なタむプがいく぀かありたす。これらは、高ランク高ランクおよび再垰構造タむプの倚態性関数のタむプです。 しかし、次の投皿でそれらに぀いお詳しく説明したす。今日は、Scalaでナニオン型を䜜成する方法を玹介したす。 説明の過皋で、Curry-Howard同型にいく぀かの光を圓お、それを目的に䜿甚する方法を瀺したす。



それでは、手始めに、ナニオン型ずは䜕ですか ナニオンのタむプは、倚くの点であなたが期埅するものです。2぀たたはそれ以䞊、ただし私は2぀だけに制限したすタむプのナニオンです。 このタむプの倀には、結合された各タむプのすべおの倀が含たれたす。 これが意味するこずは、䟋を理解するのに圹立ちたすが、最初に衚蚘法を導入したす。 すぐに明らかになる理由から、操䜜蚘号OR T √ U Uしお、タむプTずU和集合を曞き留めたすT √ U したがっお、 Int型ずString型の結合は、 Intずしお蚘述されたす。 このタむプのナニオンの倀には、 Intタむプのすべおの倀ずStringタむプのすべおの倀が含たれたす。

しかし、これは具䜓的にどういう意味ですか ぀たり、Scalaでそのような型を盎接衚珟できれば、たずえば次のように曞くこずができたす。
 def size(x: Int √ String) = x match { case i: Int => i case s: String => s.length } size(23) == 23 // OK size("foo") == 3 // OK size(1.0) //  OK,   

蚀い換えるず、 sizeメ゜ッドは、 Int型たたはString型そのサブタむプNullおよびNothingを含むの匕数を受け入れ、それ以倖は受け入れNothing 。

このタむプのプヌルを䜿甚するこずず暙準のEitherを䜿甚するこずの違いを匷調するこずが重芁です。 和型ずしお知られるEither 、サブタむプをサポヌトしない蚀語のナニオン型に類䌌しおいたす。 Eitherを䜿甚しお䟋を曞き換えるず、次のようになりたす。
 def size(x: Either[Int, String]) = x match { case Left(i) => i case Right(s) => s.length } size(Left(23)) == 23 // OK size(Right("foo")) == 3 // OK 

Either[Int, String]の型Either[Int, String]は、ナニオン型Int √ Stringモデル化できたす。これは、それらず倀の間に察応同型があるためです。 ただし、Etherタむプは、ボックス化されおいない型システムプリミティブずしおではなく、ボックス化された衚珟の远加レむダヌであるこずによっおこれを達成するこずは絶察に明らかです。 Eitherよりも優れたものを䜜成できたすEither  パッケヌゞングを必芁ずせず、予想されるすべおの静的保蚌を䜿甚せずに、Scalaでナニオン型を衚す方法を芋぀けるこずができたすか

できるこずはわかっおいたすが、結果に至る途䞭で、 Curry-Howard同型を䜿甚しお1次の論理を迂回する必芁がありたす。 この同型は、型システム内の型間の関係は、論理システム内のステヌトメント間の関係のむメヌゞず芋なすこずができるこずを瀺しおいたす逆も同様です。 話しおいる型システムず遞択した論理システムに応じお、このステヌトメントをさたざたな方法で解釈できたすが、説明ではほずんどの詳现を無芖し、簡単な䟋に焊点を圓おたす。

Scalaのようなサブタむプを持぀型システムのコンテキストでカリヌハワヌド同型を瀺したす。 亀差のタむプ A with B ず論理積 A ∧ B の間に察応があるこずに気付くかもしれたせん。 私の仮定的なタむプの統䞀 A √ B ず論理的分離䞊蚘で瀺唆されたようにA√B; サブタむプ A <: B ず論理的含意 A ⇒ B の間。 次の衚の巊の列には、Scalaで実行されるサブタむプの関係がありたすただし、蚀語で盎接衚珟されない共甚䜓型の堎合。 ⇒ 。 いずれの堎合も、このような眮換は論理的に正しい匏を提䟛したす。
AずB<AA∧B⇒A
AずB<BA∧B⇒B
A <A√BA⇒A√B
B <A√BB⇒A√B

カリヌ-ハワヌド同型の本質は、機械的な眮換プロセスが垞に正しいこずです-正しい型の匏は垞に正しい論理匏に曞き換えられ、逆の堎合も同様です。 そしおこれは、結合、分離、含意のためだけに行われたせん。 たた、吊定今日の考慮事項で重芁な操䜜や䞀般性ず存圚の量指定子を含む論理匏ぞの察応を䞀般化するこずもできたす。

吊定の远加は、私たちにずっお䜕を意味したすか 2぀のタむプの組み合わせ぀たり、 A with B には、同時にAずBの䞡方Aむンスタンスである倀がありたす。 同様に、タむプA吊定 ¬[A]ず曞くには、タむプAむンスタンスではない倀が必芁であるず仮定できたすA 吊定はScala蚀語で盎接衚珟するこずもできたせんが、そうではないず仮定しおどこに行けばいいのでしょうか

その堎合、Curry-Howard同型ずDe Morganの法則を䜿甚しお、亀差ず吊定のタむプからナニオンタむプの定矩を取埗できたす。 それがどうなるかはここにありたす...

たず、De Morganの平等を思い出しおください。
 (A √ B) ⇔ ¬(¬A ∧ ¬B) 

次に、Curry-Howard同型を適甚したすScalaの同倀=:=操䜜を䜿甚。
 (A √ B) =:= ¬[¬[A] with ¬[B]] 

これをScalaに曞き蟌む方法を芋぀けるこずができれば、目暙は達成され、さたざたな皮類の統䞀が可胜になりたす。 では、Scalaで型吊定を衚珟できたすか

残念ながら、できたせん。 しかし、できるこずは、倉換されたコンテキストで吊定を蚘述できるようにすべおの型を倉換するこずです。 次に、すべおを元の未修正のコンテキストで機胜させる方法を芋぀ける必芁がありたす。

䞀郚の読者は、以前に連結ず組み合わせた亀差タむプ、分離ず組み合わせたナニオンタむプ、および含意ず組み合わせたサブタむプ比を䜿甚しおカリヌハワヌド同型を説明したずきに少し驚いたかもしれたせん。 通垞、補品タむプ、぀たり (A, B)接続詞をモデル化し、和のタむプ Either[A, B] は遞蚀をモデル化し、関数タむプは含意をモデル化したす。 補品、合蚈、関数を䜿甚しお前の衚を曞き換えるず、次のようになりたす。
A、B=> AA∧B⇒A
A、B=> BA∧B⇒B
A => [A、B]のいずれかA⇒A√B
B =>どちらか[A、B]B⇒A√B

巊偎では、サブタむプの関係に関する正確性はもはや期埅されおいたせん。代わりに、関数のシグネチャのみに基づいお関数型を実装できるかどうかを刀断できるようにするパラメトリックの原則を芳察する必芁がありたす。 明らかに、巊の列のすべおの関数シグネチャを実装できたす。 最初の2぀のケヌスでは、関数の匕数ずしおペア(A, B)があるため、 _1たたは_2を䜿甚しおこのペアからタむプAたたはB倀を簡単に取埗できたす。
 val conj1: ((A, B)) => A = p => p._1 val conj2: ((A, B)) => B = p => p._2 

2番目ず3番目のケヌスでは、関数の匕数はそれぞれタむプAずB倀であるため、コンストラクタヌLeft[A]ずRight[B]を䜿甚しおEither[A, B]型Either[A, B]結果を取埗できたす。
 val disj1: A => Either[A, B] = a => Left(a) val disj2: B => Either[A, B] = b => Right(b) 

この圢匏で、カリヌ-ハワヌド同型は通垞、サブタむプのない蚀語で衚珟されたす。 ただし、共甚䜓タむプず亀差タむプは、本質的にサブタむプに基づいおいたす。 したがっお、考慮されたマッピングは、決しお組合を助けたせん。 しかし、それは吊定の助けになりたす。これはパズルの欠けおいる郚分です。

サブタむプの有無にかかわらず、ScalaでNothingずしお衚される最小のタむプボトムタむプは論理falseにマップされたす。 たずえば、次の等匏はすべお真です。
A =>どちらか[A、Nothing]A⇒A√false
B =>どちらか[Nothing、B]B⇒false√B

これは、巊偎のすべおの関数シグネチャが実珟可胜であり、右偎の論理匏が真であるずいう事実に基づいおいたすJames Airyの投皿では、䞀臎する補品/接続のケヌスを衚瀺しない理由を説明しおいたす。 次のシグネチャを持぀関数に察応するものに぀いお考えおみたしょう。
 A => Nothing 

カリヌ-ハワヌド同型性の右偎の論理的な偎面では、このシグネチャは数匏¬Aたす。これは¬Aず同等¬A 。 それは非垞に盎感的に合理的なようです-タむプNothingの倀がないため、シグネチャA => Nothingを実装できたせん䟋倖をスロヌするこずを陀き、この堎合は蚱可されたせん。

この眲名を型吊定の衚珟ずしお䜿甚するずどうなるか芋おみたしょう。
 type ¬[A] = A => Nothing 

そしお、De Morganの法則の文脈でそれを適甚しお、関連付けのタむプを取埗したす。
 type √[T, U] = ¬[¬[T] with ¬[U]] 

これで、Scala REPLを䜿甚しお型を確認できたす。
 scala> type ¬[A] = A => Nothing defined type alias $u00AC scala> type √[T, U] = ¬[¬[T] with ¬[U]] defined type alias $u2228 scala> implicitly[Int <:< (Int √ String)] <console>:11: error: Cannot prove that Int <:< ((Int) => Nothing with (String) => Nothing) => Nothing. implicitly[Int <:< (Int √ String)] 

REPLは、゜リュヌションがただ受信されおいないこずを瀺しおいたす。 implicitly[Int <:< (Int √ String)]な匏implicitly[Int <:< (Int √ String)]は、 IntがIntのサブタむプであるこずを蚌明できるかどうかをコンパむラヌに尋ねたす。これは、共甚䜓のタむプに圓おはたりたす。

䜕が悪かったのですか 問題は、 A => Nothingずしお指定された型吊定を䜿甚するために、 <:<挔算子の右偎の型を関数型に倉換したこずです。 これは、ナニオンのタむプ自䜓が関数のタむプであるこずを意味したす。 しかし、これは明らかに、INTがナニオン型のサブタむプであるこずずは異なり、REPLからの゚ラヌメッセヌゞが衚瀺されたす。 ゚ラヌを排陀するには、 <:<挔算子の巊偎を、右偎の型のサブタむプになる型に倉換する必芁もありたす。

これはどのような倉革ですか 二重吊定はどうですか
 type ¬¬[A] = ¬[¬[A]] 

コンパむラヌの蚀うこずを芋おみたしょう
 scala> type ¬¬[A] = ¬[¬[A]] defined type alias $u00AC$u00AC scala> implicitly[¬¬[Int] <:< (Int √ String)] res5: <:<[((Int) => Nothing) => Nothing, ((Int) => Nothing with (String) => Nothing) => Nothing] = <function1> scala> implicitly[¬¬[String] <:< (Int √ String)] res6: <:<[((String) => Nothing) => Nothing, ((Int) => Nothing with (String) => Nothing) => Nothing] = <function1> 

ビンゎ ¬¬[Int]ず¬¬[String]は䞡方ずも、 ¬¬[Int]サブタむプです。

次に、毎回肯定的な答えを返すだけではないこずを確認する必芁がありたす。
 scala> implicitly[¬¬[Double] <:< (Int √ String)] <console>:12: error: Cannot prove that ((Double) => Nothing) => Nothing <:< ((Int) => Nothing with (String) => Nothing) => Nothing. 

それで、私たちはほずんど終わりたした、最埌の仕䞊げをするこずは残っおいたす。 所有するサブタむプの関係は、取埗したいサブタむプず同型ですタむプ¬¬[T]は¬¬[T]ず同型であるため。 しかし、私たちはただ必芁な未修正の型ず同じ関係を衚珟する方法がありたせん。

この問題を解決するには、 ¬[T] 、 ¬¬[T]および¬¬[T]ファントムタむプを怜蚎し、これらのタむプの倀を盎接操䜜するのではなく、必芁なサブタむプの関係を衚すためだけに䜿甚したす。 テストケヌスでこれがどのように発生するかを次に瀺したす。
 def size[T](t: T)(implicit ev: (¬¬[T] <:< (Int √ String))) = t match { case i: Int => i case s: String => s.length } 

これは䞀般化された型制玄を䜿甚したす。これは、 sizeメ゜ッドの型匕数ずしお掚定されるTがその二重吊定がInt√Stringのサブタむプであるこずをコンパむラが蚌明するこずを芁求したす。 次のREPLセッションが瀺すように、この条件は、 TがIntたたはString堎合にのみ満たされたす。
 scala> def size[T](t: T)(implicit ev: (¬¬[T] <:< (Int √ String))) = | t match { | case i: Int => i | case s: String => s.length | } size: [T](t: T)(implicit ev: <:<[((T) => Nothing) => Nothing, ((Int) => Nothing with (String) => Nothing) => Nothing])Int scala> size(23) res8: Int = 23 scala> size("foo") res9: Int = 3 scala> size(1.0) <console>:13: error: Cannot prove that ((Double) => Nothing) => Nothing <:< ((Int) => Nothing with (String) => Nothing) => Nothing. 

そしお今、最埌のトリック。 構文に関しおは、暗黙の蚌明パラメヌタヌはtheくお重いように芋えたすが、これをT型のパラメヌタヌのコンテキスト制玄に倉換するこずで修正できたす。
 type |√|[T, U] = { type λ[X] = ¬¬[X] <:< (T √ U) } def size[T: (Int |√| String)#λ](t: T) = t match { case i: Int => i case s: String => s.length } 

できた 蚀語自䜓を倉曎するこずなく、Scalaでunion型のunpacked、staticly type-safe衚珟を取埗したした

圓然、Scalaがナニオン型をプリミティブずしおサポヌトする方が良いでしょう。 しかし、少なくずも私たちが埗た解決策は、Scalaコンパむラがこれを行うために必芁なすべおの情報を持っおいるこずを瀺しおいたす。 今ではマヌティンずアドリアヌンを悩たせおいるだけなので、統䞀のタむプを盎接利甚できたす。

Source: https://habr.com/ru/post/J269907/


All Articles