Data.Void'deki saçma işlev ne işe yarar?


97

İçindeki absurdişlev Data.Voidaşağıdaki imzaya sahiptir; burada Void, bu paket tarafından dışa aktarılan mantıksal olarak yerleşimsiz tür:

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

Belgelerin, bunun tür olarak önermeler ile geçerli formüle karşılık geldiğine dair açıklamasını almak için yeterli mantık biliyorum ⊥ → a.

Şaşkın ve merak ettiğim şey şu: Bu işlev ne tür pratik programlama problemlerinde kullanışlıdır? Belki de bazı durumlarda, "olamaz" vakalarını kapsamlı bir şekilde ele almanın tür güvenli bir yolu olarak faydalı olabileceğini düşünüyorum, ancak Curry-Howard'ın pratik kullanımları hakkında bu fikrin geçerli olup olmadığını söyleyecek kadar bilgim yok. kesinlikle doğru yol.

DÜZENLEME: Örnekler tercihen Haskell'de, ancak herhangi biri bağımlı olarak yazılmış bir dil kullanmak isterse şikayet etmeyeceğim ...


5
Hızlı bir arama, absurdişlevin bu makalede Contmonad ile ilgili olarak kullanıldığını gösteriyor : haskellforall.com/2012/12/the-continuation-monad.html
Artyom

6
Ve absurdarasındaki izomorfizmin bir yönü olarak görebilirsiniz . Voidforall a. a
Daniel Wagner

Yanıtlar:


61

Haskell katı olmadığı için hayat biraz zor. Genel kullanım örneği, imkansız yolları ele almaktır. Örneğin

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

Bu biraz işe yarıyor. İçin basit bir tür düşününPipes

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

bu Gabriel Gonzales'in Pipeskütüphanesinden standart boru tipinin katı ve basitleştirilmiş bir sürümüdür . Şimdi, asla vermeyen (yani bir tüketici) bir boruyu şu şekilde kodlayabiliriz:

type Consumer a r = Pipe a Void r

bu gerçekten asla sonuç vermez. Bunun anlamı bir için uygun kat kural olmasıdır Consumerolduğunu

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

veya alternatif olarak, tüketicilerle uğraşırken getiri durumunu görmezden gelebilirsiniz . Bu, bu tasarım modelinin genel versiyonudur: polimorfik veri türlerini kullanın ve Voidihtiyaç duyduğunuzda olasılıklardan kurtulun.

Muhtemelen en klasik kullanımı VoidCPS'dedir.

type Continuation a = a -> Void

yani, a Continuationasla geri dönmeyen bir fonksiyondur. Continuation"not" türünün tür sürümüdür. Bundan bir CPS monadı elde ederiz (klasik mantığa karşılık gelir)

newtype CPS a = Continuation (Continuation a)

Haskell saf olduğu için bu türden hiçbir şey çıkaramıyoruz.


1
Huh, aslında o CPS bitini takip edebilirim. Curry-Howard çifte olumsuzlama / CPS yazışmalarını daha önce kesinlikle duymuştum ama anlamadım; Şimdi tam olarak anladığımı iddia etmeyeceğim, ama bu kesinlikle yardımcı oluyor!
Luis Casillas

5
@ErikAllik, katı bir dille, Voidinsansızdır. Haskell'de içerir _|_. Katı bir dilde, türden bir bağımsız değişken alan bir veri oluşturucu Voidhiçbir zaman uygulanamaz, bu nedenle model eşleşmesinin sağ tarafına erişilemez. Haskell'de !bunu uygulamak için a kullanmanız gerekir ve GHC muhtemelen yolun erişilemez olduğunu fark etmez.
dfeuer

1
agda genel olarak toplamdır ve bu nedenle değerlendirme sırası gözlemlenebilir değildir. Sonlandırma denetleyicisini veya benzeri bir şeyi kapatmadığınız sürece boş tipte kapalı bir agda terimi yoktur
Philip JF

1
@jcalz ipucu: argümana iletilecek bir şey bulmak yerine, onu türde a -> Voidbir işlev ve türde bir işlev oluşturmak için kullanın (a -> Void) -> Void.
PyRulez

1
@PyRulez teşekkürler! Geriye dönüp baktığımda cevap o kadar açık görünüyor ki önceden neden kaçırdığımı anlayamıyorum.
jcalz

57

Serbest değişkenleri tarafından parametrelendirilen lambda terimleri için bu gösterimi düşünün. (Bellegarde ve Hook 1994, Bird ve Paterson 1999, Altenkirch ve Reus 1999'un makalelerine bakın.)

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

Bunu kesinlikle Functoryeniden adlandırma Monadfikrini ve ikame kavramını yakalayan bir hale getirebilirsiniz .

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))

Şimdi kapalı şartları düşünün : bunlar sakinleridir Tm Void. Kapalı terimleri rastgele serbest değişkenler içeren terimlere yerleştirebilmelisiniz. Nasıl?

fmap absurd :: Tm Void -> Tm a

İşin püf noktası, elbette, bu işlevin hiçbir şey yapmadan terimi aşacak olmasıdır. Ama daha dürüst bir dokunuş unsafeCoerce. Ve bu yüzden vacuouseklendi Data.Void...

Veya bir değerlendirici yazın. Serbest değişkenli değerler burada b.

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

Lambdaları kapanışlar olarak temsil ettim. Değerlendirici, serbest değişkenleri aüzerinden değerlere eşleyen bir ortam tarafından parametrelendirilir 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

Tahmin ettin. Herhangi bir hedefte kapalı bir terimi değerlendirmek için

eval absurd :: Tm Void -> Val b

Daha genel olarak, Voidnadiren kendi başına kullanılır, ancak bir çeşit imkansızlığı gösteren bir şekilde bir tür parametresini somutlaştırmak istediğinizde kullanışlıdır (örneğin burada, kapalı bir terimde serbest bir değişken kullanarak). Genellikle bu parametrized tipleri daha yüksek dereceden fonksiyonlar bütün türüne operasyonlara parametrelere kaldırma işlemlerindeki ile gelen (örneğin, burada, fmap, >>=, eval). Yani absurdgenel amaçlı operasyon olarak geçiyorsunuz Void.

Başka bir örnek için, Either e vsize bir vtür istisnası getirebileceğini umduğumuz hesaplamaları yakalamak için kullanmayı hayal edin e. Kötü davranış riskini aynı şekilde belgelemek için bu yaklaşımı kullanabilirsiniz. Gayet iyi bu ayarda hesaplamaları davranmış için almak eolmaya Voidardından kullanımını

either absurd id :: Either Void v -> v

güvenle koşmak veya

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

güvenli bileşenleri güvenli olmayan bir dünyaya yerleştirmek.

Oh, ve son bir ani bir "olamaz" ı ele almak. İmlecin olamayacağı her yerde jenerik fermuar yapısında ortaya çıkıyor.

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!

Tam olarak alakalı olmasa da geri kalanını silmemeye karar verdim.

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)

Aslında, belki alakalı. Kendinizi maceraperest hissediyorsanız, bu bitmemiş makale , Voidterimlerin temsilini serbest değişkenlerle nasıl sıkıştıracağınızı gösterir.

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

a Differentiableand Traversablefunctor'dan serbestçe üretilen herhangi bir sözdiziminde f. Kullandığımız Term f Voidserbest değişkenlerle bölgeleri temsil etmek ve [D f (Term f Void)]göstermek için boruları izole edilmiş bir serbest değişkene, ya da iki ya da daha fazla serbest değişkenlere yollarında bir birleşme ya serbest değişkenlerle bölgeleri ile tünel. Bu makaleyi bir ara bitirmelisin.

Değeri olmayan bir tür için (veya en azından kibar bir şirkette konuşmaya değmez), Voidoldukça kullanışlıdır. Ve absurdbunu nasıl kullandığın.


Misiniz forall f. vacuous f = unsafeCoerce fgeçerli bir GHC yeniden yazma kuralı olacak?
Cactus

1
@Cactus, gerçekten değil. Sahte Functorörnekler, aslında functor gibi olmayan GADT'ler olabilir.
dfeuer

Bu misiniz Functorler kırılmaz fmap id = idkural? Yoksa burada "sahte" ile kastettiğin bu mu?
Cactus

34

"Olmaz" vakalarını kapsamlı bir şekilde ele almanın tür açısından güvenli bir yolu olarak bazı durumlarda yararlı olabileceğini düşünüyorum.

Bu kesinlikle doğru.

Bunun absurddaha kullanışlı olmadığını söyleyebilirsin const (error "Impossible"). Bununla birlikte, tür sınırlıdır, bu nedenle tek girişi Void, kasıtlı olarak ıssız bırakılan bir veri türü olan türde bir şey olabilir . Bu, geçebileceğiniz gerçek bir değer olmadığı anlamına gelir absurd. Yazım denetleyicisinin türden bir şeye erişiminiz olduğunu düşündüğü bir kod dalına düşerseniz Void, o zaman saçma bir durumdasınız demektir. Yani absurdbasitçe bu kod dalına asla ulaşılmaması gerektiğini işaretlemek için kullanın .

"Ex falso quodlibet" kelimenin tam anlamıyla "[a] yanlış [önerme] 'den, her şey takip eder" anlamına gelir. Dolayısıyla, türü olan bir veriyi tuttuğunuzu fark ettiğinizde, Voidellerinizde yanlış kanıtlar olduğunu bilirsiniz. Bu nedenle, istediğiniz boşluğu (aracılığıyla absurd) doldurabilirsiniz çünkü yanlış bir önermeden her şey gelir.

Kullanım örneği olan Conduit'in arkasındaki fikirler hakkında bir blog yazısı yazdım absurd.

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


13

Genellikle, görünüşte kısmi kalıp eşleşmelerinden kaçınmak için kullanabilirsiniz. Örneğin, bu yanıttan veri türü bildirimlerinin bir tahmini elde edilir :

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

O zaman şöyle kullanabilirsiniz absurd, örneğin:

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

13

Boş veri tipini göstermenin farklı yolları vardır . Biri boş bir cebirsel veri türüdür. Başka bir yol da onu ∀α.α için bir takma ad yapmaktır veya

type Void' = forall a . a

Haskell'de - onu Sistem F'de bu şekilde kodlayabiliriz (bkz. Kanıtlar ve Türler Bölüm 11 ). Bu iki açıklamaları elbette izomorfik ve İzomorfizma tanıklık eder \x -> x :: (forall a.a) -> Voidve tarafından absurd :: Void -> a.

Bazı durumlarda, genellikle boş veri türü bir işlevin bağımsız değişkeninde veya Data.Conduit gibi daha karmaşık bir veri türünde görünüyorsa açık değişkeni tercih ederiz :

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

Bazı durumlarda, polimorfik varyantı tercih ederiz, genellikle boş veri tipi bir fonksiyonun dönüş tipinde yer alır.

absurd bu iki temsil arasında dönüşüm yaptığımızda ortaya çıkar.


Örneğin, callcc :: ((a -> m b) -> m a) -> m akullanır (örtük) forall b. Bu tip de olabilir ((a -> m Void) -> m a) -> m a, çünkü sürekliliğe yapılan bir çağrı aslında geri dönmez, kontrolü başka bir noktaya aktarır. Sürekliliklerle çalışmak isteseydik, tanımlayabilirdik

type Continuation r a = a -> Cont r Void

(Biz kullanabilirsiniz type Continuation' r a = forall b . a -> Cont r bama bu rütbe 2 tip gerektirir ediyorum.) Ve sonra, vacuousMbu dönüştürür Cont r Voidiçine Cont r b.

(Ayrıca , void paketini kimin ve nasıl kullandığını görmek gibi belirli bir paketin kullanımını (ters bağımlılıklar) aramak için haskellers.com'u kullanabileceğinizi unutmayın .)


TypeApplicationsayrıntıları hakkında daha açıkça belirtilmesi amacıyla kullanılabilir proof :: (forall a. a) -> Void: proof fls = fls @Void.
Iceland_jack

1

Idris gibi bağımlı olarak yazılmış dillerde, muhtemelen Haskell'den daha kullanışlıdır. Tipik olarak, bir toplam işlevde, aslında işlevin içine itilemeyen bir değerle desen eşleştirdiğinizde, daha sonra ıssız türde bir değer oluşturur absurdve vaka tanımını sonlandırmak için kullanırsınız .

Örneğin, bu işlev, orada mevcut olan tür düzeyinde maliyetle bir listeden bir öğeyi kaldırır:

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

İkinci durum, boş bir listede belirli bir unsur olduğunu söylüyorsa, ki bu çok saçma. Bununla birlikte, genel olarak, derleyici bunu bilmez ve çoğu zaman açık olmalıyız. Ardından derleyici, işlev tanımının kısmi olmadığını kontrol edebilir ve daha güçlü derleme zamanı garantileri elde ederiz.

Curry-Howard bakış açısına göre, önermeler nerede ise, o zaman absurdçelişkili bir kanıt olarak bir tür QED'dir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.