SMLデータ型のサブセットとしてのサブタイプ


10

純粋に機能的なデータ構造に関する岡崎の本で私が嫌いないくつかのことの1つは、彼のコードが網羅的なパターンマッチングで散らかされていることです。例として、彼にリアルタイムキューの実装を示します(不要な中断を排除するためにリファクタリングしました)。

infixr 5 :::

datatype 'a stream = Nil | ::: of 'a * 'a stream lazy

structure RealTimeQueue :> QUEUE =
struct
  (* front stream, rear list, schedule stream *)
  type 'a queue = 'a stream * 'a list * 'a stream

  (* the front stream is one element shorter than the rear list *)
  fun rotate (x ::: $xs, y :: ys, zs) = x ::: $rotate (xs, ys, y ::: $zs)
    | rotate (Nil, y :: nil, zs) = y ::: $zs

  fun exec (xs, ys, _ ::: $zs) = (xs, ys, zs)
    | exec args = let val xs = rotate args in (xs, nil, xs) end

  (* public operations *)
  val empty = (Nil, nil, Nil)
  fun snoc ((xs, ys, zs), y) = exec (xs, y :: ys, zs)
  fun uncons (x ::: $xs, ys, zs) = SOME (x, exec (xs, ys, zs))
    | uncons _ = NONE
end

図から分かるようにrotate、それはリアリストが空の場合をカバーしていないので、網羅的なものではありません。ほとんどの標準ML実装は、それに関する警告を生成します。我々はので、リアリストはおそらく空にすることはできませんことを知っているrotateの前提条件は、そのリアリスト1の要素長いフロントストリームを超えています。しかし、型チェッカーは知りません。そして、MLの型システムではこの事実を表現できないため、それを知ることはできません。

現在、この警告を抑制するための私の解決策は、次の洗練されていないハックです。

  fun rotate (x ::: $xs, y :: ys, zs) = x ::: $rotate (xs, ys, y ::: $zs)
    | rotate (_, ys, zs) = foldl (fn (x, xs) => x ::: $xs) zs ys

しかし、私が本当に望んでいるのは、すべてのトリプレットがの有効な引数であるとは限らないことを理解できる型システムですrotate。型システムに次のような型を定義させたい:

type 'a triplet = 'a stream * 'a list * 'a stream

subtype 'a queue of 'a triplet
  = (Nil, nil, Nil)
  | (xs, ys, zs) : 'a queue => (_ ::: $xs, _ :: ys, zs)
  | (xs, ys, zs) : 'a queue => (_ ::: $xs, ys, _ ::: $zs)

そして推論:

subtype 'a rotatable of 'a triplet
  = (xs, ys, _) : 'a rotatable => (_ ::: $xs, _ :: ys, _)
  | (Nil, y :: nil, _)

subtype 'a executable of 'a triplet
  = (xs, ys, zs) : 'a queue => (xs, ys, _ ::: $zs)
  | (xs, ys, Nil) : 'a rotatable => (xs, ys, Nil)

val rotate : 'a rotatable -> 'a stream
val exec : 'a executable -> 'a queue

ただし、本格的な依存型、GADT、または特定のプログラマーが使用するその他のクレイジーなものは必要ありません。私は、既存のMLタイプの帰納的に定義されたサブセットを「切り出す」ことによってサブタイプを定義したいだけです。これは実現可能ですか?

回答:


20

これらの種類のタイプ-受け入れ可能な値の文法を与えることによってサブタイプを(基本的に)定義する場所-は、データソート絞り込みと呼ばれます。

  • これらは、Tim FreemanとFrank Pfenningによって、1991年のPLDI論文、Refinement Types for MLで紹介されました。

  • Rowan Daviesは、彼の博士論文「Practical Refinement Type Checking」で改良型の型推論を研究しました。彼はそれをSMLの拡張としても実装しましたが、それがオンラインで利用可能かどうかはわかりません。

  • ジョシュアダンフィールドは、データソートの絞り込みと、より洗練された軽量の型依存性を組み合わせる方法を研究しました。彼の論文、A Unified System of Type Refinementsです。また、オンラインで入手できるスターダスト言語で実装しました。

    http://www.mpi-sws.org/~joshua/stardust/


3
Rowan Daviesの実装はこちらから入手できます:github.com/rowandavies/sml-cidre
Noam Zeilberger

1

GADT、TypeFamilies、DataKinds、およびTypeOperators(美学のためだけ)を使用して、目的を作成できます。

data Term0 varb lamb letb where
    Lam :: lamb -> Term0 varb lamb letb -> Term0 varb lamb letb
    Let :: letb -> Term0 varb lamb letb -> Term0 varb lamb letb -> Term0 varb lamb letb
    Var :: varb -> Term0 varb lamb letb
    App :: Term0 varb lamb letb -> Term0 varb lamb letb -> Term0 varb lamb letb

type Term b = Term0 b b b

data Terms = Lets | Lams | Vars

type family  t /// (ty :: Terms) where
    Term0 a b c /// Vars = Term0 Void b c
    Term0 a b c /// Lams = Term0 a Void c
    Term0 a b c /// Lets = Term0 a b Void

Now, I can write functions with more refined types:

unlet :: Term b -> Term b /// Lets

ご回答有難うございます。私はGHCがTypeFamilies純粋に原理的な理由で嫌いです。それはパラメトリック性と自由定理を破壊します。GADT与えられたので、私は、またGADTsとあまり快適ではないよFoo a、あなたは2つの同型型を持つことができるBarQux、そのようにFoo BarFoo Qux同型ではありません。これは、関数mapがequalsとequalsに等しいという数学的な直観に矛盾します。また、型レベルでは、同型写像は同等の正しい概念です。
2016

私はあなたの言い分を理解していますが、それは専門化された一般化を可能にします。
Samuel Schlesinger
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.