Data.Voidの不条理な関数は何に役立ちますか?


97

absurd関数にData.Voidは次のシグネチャがあります。Voidは、そのパッケージによってエクスポートされる論理的に無人のタイプです。

-- | Since 'Void' values logically don't exist, this witnesses the logical
-- reasoning tool of \"ex falso quodlibet\".
absurd :: Void -> a

型としての命題の対応によって、これが有効な式に対応するというドキュメントの発言を得るのに十分なロジックを知っています⊥ → a

私が戸惑い、興味をそそられるのは、この関数がどのような実用的なプログラミング問題で役立つのかということです。「起こりえない」ケースを徹底的に処理するタイプセーフな方法としていくつかのケースで有用だと思いますが、そのアイデアがまったく正しい道。

編集:できればHaskellでの例が望ましいですが、誰かが依存型付けされた言語を使用したい場合は、文句は言いません...


5
クイック検索はabsurdContモナドを扱うこの記事で関数が使用されたことを示しています:haskellforall.com/2012/12/the-continuation-monad.html
Artyom

6
とのabsurd間の同型の1つの方向として見ることができます。Voidforall a. a
Daniel Wagner

回答:


61

Haskellは厳密ではないので、人生は少し難しいです。一般的な使用例は、不可能なパスを処理することです。例えば

simple :: Either Void a -> a
simple (Left x) = absurd x
simple (Right y) = y

これはやや便利であることがわかります。の単純なタイプを考えますPipes

data Pipe a b r
  = Pure r
  | Await (a -> Pipe a b r)
  | Yield !b (Pipe a b r)

これは、Gabriel GonzalesのPipesライブラリからの標準パイプタイプを厳密に簡略化したバージョンです。これで、決して生成しないパイプ(つまり、コンシューマー)を次のようにエンコードできます。

type Consumer a r = Pipe a Void r

これは本当に譲らない。これは、aの適切な折りたたみルールConsumer

foldConsumer :: (r -> s) -> ((a -> s) -> s) -> Consumer a r -> s
foldConsumer onPure onAwait p 
 = case p of
     Pure x -> onPure x
     Await f -> onAwait $ \x -> foldConsumer onPure onAwait (f x)
     Yield x _ -> absurd x

または、消費者を扱う際に利回りのケースを無視できること。これは、このデザインパターンの一般的なバージョンです。ポリモーフィックデータ型を使用し、Voidし、必要に応じて可能性を排除します。

おそらく最も古典的な使用法VoidはCPSです。

type Continuation a = a -> Void

つまり、a Continuationは決して戻らない関数です。 Continuation「not」のタイプバージョンです。これから、CPSのモナドを取得します(古典的な論理に対応)

newtype CPS a = Continuation (Continuation a)

Haskellは純粋なので、このタイプから何も得ることができません。


1
ええと、私は実際にそのCPSビットをちょっとフォローすることができます。私は確かにカリー・ハワードの二重否定/ CPSの対応を以前に聞いたことがありますが、理解できませんでした。今では完全に入手したと主張するつもりはありませんが、これは確かに役立ちます!
Luis Casillas 2013年

「Haskellは厳密ではないので、人生は少し難しいです」—それは正確に何を意味しているのですか?
Erik Kaplun、2015年

4
@ErikAllikは厳格な言語でVoidあり、無人です。Haskellでは、それは含まれています_|_。厳密な言語では、typeの引数を取るデータコンストラクターはVoid適用できないため、パターンマッチの右側には到達できません。Haskellでは、それ!を強制するためにを使用する必要があり、GHCはおそらくパスが到達不能であることを認識しません。
dfeuer 2015年

アグダはどうですか?それは怠惰ですが、それはあり_|_ますか?そしてそれは同じ制限に悩まされますか?
Erik Kaplun、2015年

agdaは一般的に言って合計なので、評価の順序は観察できません。終了チェッカーなどをオフにしない限り、空のタイプの閉じたagda項はありません
フィリップJF

58

自由変数によってパラメーター化されたラムダ項のこの表現を検討してください。(Bellegarde and Hook 1994、Bird and Paterson 1999、Altenkirch and Reus 1999の論文を参照してください。)

data Tm a  = Var a
           | Tm a :$ Tm a
           | Lam (Tm (Maybe a))

確かにこれをa Functorにして、名前変更Monadの概念を取り込んだり、置換の概念を取り込んだりできます。

instance Functor Tm where
  fmap rho (Var a)   = Var (rho a)
  fmap rho (f :$ s)  = fmap rho f :$ fmap rho s
  fmap rho (Lam t)   = Lam (fmap (fmap rho) t)

instance Monad Tm where
  return = Var
  Var a     >>= sig  = sig a
  (f :$ s)  >>= sig  = (f >>= sig) :$ (s >>= sig)
  Lam t     >>= sig  = Lam (t >>= maybe (Var Nothing) (fmap Just . sig))

次に、閉じた条件を考えます。これらはの住民ですTm Void。閉じた条件を任意の自由変数を持つ条件に埋め込むことができるはずです。どうやって?

fmap absurd :: Tm Void -> Tm a

もちろん、問題は、この関数が正確に何もしないという用語をトラバースすることです。しかし、それはより正直な感じですunsafeCoerce。そしてそれvacuousがに追加された理由ですData.Voidです...

または、エバリュエーターを作成します。以下は、の自由変数の値ですb

data Val b
  =  b :$$ [Val b]                              -- a stuck application
  |  forall a. LV (a -> Val b) (Tm (Maybe a))   -- we have an incomplete environment

ラムダをクロージャーとして表現したところです。エバリュエーターは、自由変数をaを超える値にマッピングする環境によってパラメーター化されますb

eval :: (a -> Val b) -> Tm a -> Val b
eval g (Var a)   = g a
eval g (f :$ s)  = eval g f $$ eval g s where
  (b :$$ vs)  $$ v  = b :$$ (vs ++ [v])         -- stuck application gets longer
  LV g t      $$ v  = eval (maybe v g) t        -- an applied lambda gets unstuck
eval g (Lam t)   = LV g t

当たってるよ。任意のターゲットでクローズドタームを評価するには

eval absurd :: Tm Void -> Val b

より一般的にVoidは、単独で使用されることはほとんどありませんが、ある種の不可能なことを示す方法で型パラメーターをインスタンス化する場合に便利です(たとえば、ここでは、閉じた条件で自由変数を使用します)。多くの場合、これらのパラメーター化された型には、パラメーターの操作を型全体の操作に持ち上げる高次関数が付属しています(たとえば、ここでfmap>>=、、、eval)。したがってabsurd、の汎用操作として渡されますVoid

別の例として、使用して想像Either e vできればあなたを与える計算キャプチャするvが、種類の例外を発生させるかもしれませんがe。このアプローチを使用して、不正な動作のリスクを均一に文書化できます。この設定で完全に適切に動作する計算を行うeにはVoid、次のようにしてから使用します

either absurd id :: Either Void v -> v

安全に実行するか

either absurd Right :: Either Void v -> Either e v

安全でないコンポーネントを安全でない世界に埋め込む。

ああ、そして最後にもう1つ、「起こり得ないこと」を扱います。これは、カーソルが配置できないすべての場所で、一般的なジッパー構造に表示されます。

class Differentiable f where
  type D f :: * -> *              -- an f with a hole
  plug :: (D f x, x) -> f x       -- plugging a child in the hole

newtype K a     x  = K a          -- no children, just a label
newtype I       x  = I x          -- one child
data (f :+: g)  x  = L (f x)      -- choice
                   | R (g x)
data (f :*: g)  x  = f x :&: g x  -- pairing

instance Differentiable (K a) where
  type D (K a) = K Void           -- no children, so no way to make a hole
  plug (K v, x) = absurd v        -- can't reinvent the label, so deny the hole!

完全に関連性はありませんが、残りを削除しないことにしました。

instance Differentiable I where
  type D I = K ()
  plug (K (), x) = I x

instance (Differentiable f, Differentiable g) => Differentiable (f :+: g) where
  type D (f :+: g) = D f :+: D g
  plug (L df, x) = L (plug (df, x))
  plug (R dg, x) = R (plug (dg, x))

instance (Differentiable f, Differentiable g) => Differentiable (f :*: g) where
  type D (f :*: g) = (D f :*: g) :+: (f :*: D g)
  plug (L (df :&: g), x) = plug (df, x) :&: g
  plug (R (f :&: dg), x) = f :&: plug (dg, x)

実際には、多分それは関連しています。冒険したい場合は、この未完成の記事Voidで、自由変数を使用して用語の表現を圧縮する方法を示します。

data Term f x = Var x | Con (f (Term f x))   -- the Free monad, yet again

DifferentiableおよびTraversableファンクタから自由に生成された構文でf。我々が使用しTerm f Voidない自由変数と領域を表すために、そして[D f (Term f Void)]表現するためにを単離し自由変数に、または二つ以上の自由変数へのパスにおける接合のいずれかにない自由変数と領域を介してトンネリング。いつかその記事を終えなければならない。

値のない型(または、少なくとも、丁寧な会社で話す価値がない型)の場合、Void非常に役立ちます。そしてそれabsurdはあなたがそれを使う方法です。


forall f. vacuous f = unsafeCoerce f、有効なGHCの書き換えルールも?
サボテン

1
@サボテン、そうではない。偽のFunctorインスタンスは、実際にはファンクタのようなものではないGADTである可能性があります。
dfeuer 2015年

それらFunctorfmap id = idルールに違反しませんか?それとも、ここで「偽物」とはどういう意味ですか?
サボテン

35

「起こりえない」ケースを徹底的に処理するタイプセーフな方法として、場合によっては役立つと思います

これはまさに正しいです。

それabsurdはと同じくらい便利ですconst (error "Impossible")。ただし、タイプは制限されているため、その唯一の入力Voidは、意図的に無人のままにしておくデータ型であるタイプの何かにすることができます。つまり、に渡すことができる実際の値はありませんabsurd。型チェッカーが型の何かへのアクセス権を持っているとコードで判断した場合Void不合理な状況に陥ります。したがってabsurd、基本的には、このコードブランチに到達してはならないことをマークするために使用します。

「Ex falso quodlibet」は、文字通り「[a] false [命題]から、何かが続く」という意味です。したがって、タイプがのデータの一部を保持しVoidていることがわかると、誤った証拠が手元にあることがわかります。したがって、(を介して)必要穴を埋めることができabsurdます。これは、誤った命題から、何かが続くためです。

の使用例を含む、Conduitの背後にあるアイデアについてブログ投稿しましたabsurd

http://unknownparallel.wordpress.com/2012/07/30/pipes-to-conduits-part-6-leftovers/#running-a-pipeline


13

通常、これを使用して、明らかに部分的なパターン一致を回避できます。たとえば、この回答からデータ型宣言の近似を取得します

data RuleSet a            = Known !a | Unknown String
data GoRuleChoices        = Japanese | Chinese
type LinesOfActionChoices = Void
type GoRuleSet            = RuleSet GoRuleChoices
type LinesOfActionRuleSet = RuleSet LinesOfActionChoices

次にabsurd、たとえば次のように使用できます。

handleLOARules :: (String -> a) -> LinesOfActionsRuleSet -> a
handleLOARules f r = case r of
    Known   a -> absurd a
    Unknown s -> f s

13

空のデータ型を表現する方法はいくつかあります。1つは空の代数データ型です。もう1つの方法は、.α.αのエイリアスにするか、

type Void' = forall a . a

Haskell-これは、システムFでエンコードする方法です(証明と型の第11章を参照)。これら2つの記述はもちろん同型であり、同型はとによって証明さ\x -> x :: (forall a.a) -> Voidabsurd :: Void -> aます。

場合によっては、通常、関数の引数に、またはData.Conduitなどのより複雑なデータ型に空のデータ型が表示される場合、明示的なバリアントを使用します

type Sink i m r = Pipe i i Void () m r

場合によっては、ポリモーフィックバリアントが好まれます。通常、関数の戻り値の型には空のデータ型が含まれます。

absurd これらの2つの表現の間で変換するときに発生します。


たとえば、callcc :: ((a -> m b) -> m a) -> m a(暗黙)を使用しforall bます。((a -> m Void) -> m a) -> m a継続の呼び出しは実際には返されないので、タイプの場合もあります。制御を別のポイントに移します。継続を処理したい場合は、次のように定義できます。

type Continuation r a = a -> Cont r Void

(使用できますtype Continuation' r a = forall b . a -> Cont r bが、ランク2タイプが必要です。)次に、vacuousMこれCont r Voidをに変換しCont r bます。

(また、haskellers.comを使用して、特定のパッケージの使用状況(逆依存関係)を検索して、誰がどのようにvoidパッケージを使用するかを確認することもできます。)


TypeApplicationsの詳細についてより明確にするために使用することができますproof :: (forall a. a) -> Voidproof fls = fls @Void
Iceland_jack

1

Idrisのような依存型付きの言語では、おそらくHaskellよりも有用です。通常、合計関数では、実際に関数に押し込むことができない値にパターン一致する場合、無人型の値を作成し、それを使用absurdしてケース定義を完成させます。

たとえば、次の関数は、リストレベルから存在するタイプレベルのコストレインを持つリストから要素を削除します。

shrink : (xs : Vect (S n) a) -> Elem x xs -> Vect n a
shrink (x :: ys) Here = ys
shrink (y :: []) (There p) = absurd p
shrink (y :: (x :: xs)) (There p) = y :: shrink (x :: xs) p

2番目のケースでは、空のリストに特定の要素があると言っていますが、これは不合理です。ただし、一般に、コンパイラーはこれを認識していないため、明示的にする必要があります。次に、コンパイラーは関数定義が部分的でないことを確認でき、より強力なコンパイル時の保証が得られます。

カレー・ハワードの見解を通じて、命題はどこにあるかはabsurd、矛盾による証明の一種のQEDです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.