2013-01-03 29 views
81

Các absurd chức năng trong Data.Void có chữ ký sau đây, nơi Void là loại logic không có người ở xuất khẩu bằng cách gói:Chức năng vô lý trong Data.Void là gì?

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

tôi biết đủ logic để có được nhận xét của tài liệu này tương ứng, bởi mệnh đề-như dạng tương ứng, với công thức hợp lệ ⊥ → a.

Điều tôi đang bối rối và tò mò là: trong những loại vấn đề lập trình thực tế nào là chức năng này hữu ích? Tôi nghĩ rằng có lẽ nó hữu ích trong một số trường hợp như một cách an toàn để xử lý triệt để những trường hợp "không thể xảy ra", nhưng tôi không biết đủ về cách sử dụng thực tế của Curry-Howard để nói liệu ý tưởng đó có trên đúng đường.

EDIT: Ví dụ tốt nhất là trong Haskell, nhưng nếu ai muốn sử dụng một ngôn ngữ lệ thuộc gõ Tôi sẽ không khiếu nại ...

+5

Tìm kiếm nhanh cho thấy hàm 'absurd' đã được sử dụng trong bài viết này đối phó với đơn nguyên' Cont': http://www.haskellforall.com/2012/12/the-continuation-monad.html – Artyom

+6

Bạn có thể thấy 'ngớ ngẩn' là một hướng của sự đẳng cấu giữa 'Void' và' forall a. a'. –

Trả lời

49

Cuộc sống là một chút khó khăn, vì Haskell là phi nghiêm khắc. Trường hợp sử dụng chung là xử lý các đường dẫn không thể. Ví dụ:

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

Điều này hóa ra hơi hữu ích.Hãy xem xét một kiểu đơn giản cho Pipes

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

đây là một phiên bản nghiêm ngặt-ified và đơn giản của các đường ống tiêu chuẩn loại từ thư viện Pipes Gabriel Gonzales'. Bây giờ, chúng tôi có thể mã hóa một đường ống không bao giờ mang lại (ví dụ: người tiêu dùng) là

type Consumer a r = Pipe a Void r 

điều này thực sự không bao giờ mang lại lợi nhuận. Hàm ý của việc này là sự cai trị gấp thích hợp cho một 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 

hoặc cách khác, bạn có thể bỏ qua trường hợp năng suất khi giao dịch với người tiêu dùng. Đây là phiên bản chung của mẫu thiết kế này: sử dụng các kiểu dữ liệu đa hình và Void để loại bỏ các khả năng khi bạn cần.

Có lẽ cách sử dụng cổ điển nhất là Void là trong CPS.

type Continuation a = a -> Void 

có nghĩa là, một Continuation là một chức năng mà không bao giờ trở lại. Continuation là phiên bản loại "không". Từ điều này, chúng tôi có được một đơn vị của CPS (tương ứng với logic cổ điển)

newtype CPS a = Continuation (Continuation a) 

vì Haskell là tinh khiết, chúng tôi không thể lấy bất kỳ thứ gì ra khỏi loại này.

+1

Huh, tôi thực sự có thể thực hiện theo bit CPS đó. Tôi chắc chắn đã từng nghe về hai lá thư từ chối/CPS của Curry-Howard trước đây, nhưng không hiểu nó; Tôi sẽ không yêu cầu tôi hoàn toàn có được nó ngay bây giờ, nhưng điều này chắc chắn sẽ giúp! –

+0

_ "Cuộc sống hơi khó khăn một chút, vì Haskell không nghiêm ngặt" - ý bạn là gì? –

+1

@ErikAllik, bằng ngôn ngữ nghiêm ngặt, 'Void' không có người ở. Trong Haskell, nó chứa '_ | _'. Trong một ngôn ngữ nghiêm ngặt, một nhà xây dựng dữ liệu mà có một đối số của loại 'Void' không bao giờ có thể được áp dụng, do đó, bên tay phải của mô hình phù hợp là không thể truy cập. Trong Haskell, bạn cần sử dụng '!' Để thực thi điều đó, và GHC có thể sẽ không nhận thấy rằng đường dẫn không thể truy cập được. – dfeuer

32

Tôi đang nghĩ rằng có lẽ đó là hữu ích trong một số trường hợp như một cách an toàn loại xử lý triệt để "không thể xảy ra" trường hợp

Điều này là chính xác.

Bạn có thể nói rằng absurd không hữu ích hơn const (error "Impossible"). Tuy nhiên, nó là loại bị hạn chế, do đó, đầu vào duy nhất của nó có thể là một cái gì đó loại Void, một kiểu dữ liệu cố ý không có người ở. Điều này có nghĩa là không có giá trị thực tế nào mà bạn có thể chuyển đến absurd. Nếu bạn từng kết thúc bằng một chi nhánh mã nơi trình kiểm tra loại cho rằng bạn có quyền truy cập vào một loại nào đó theo số Void, thì, bạn đang ở trong tình huống vô lý. Vì vậy, bạn chỉ cần sử dụng absurd để cơ bản đánh dấu rằng chi nhánh mã này sẽ không bao giờ được tiếp cận.

"Ex falso quodlibet" nghĩa đen là "từ [a] sai [đề xuất], bất kỳ điều gì sau". Vì vậy, khi bạn thấy rằng bạn đang nắm giữ một mẩu dữ liệu có loại là Void, bạn biết bạn có bằng chứng giả trong tay của bạn. Do đó, bạn có thể điền bất kỳ lỗ nào mà bạn muốn (thông qua absurd), bởi vì từ một mệnh đề sai, bất kỳ điều gì sau đây.

Tôi đã viết một bài đăng blog về những ý tưởng đằng sau Conduit có ví dụ về cách sử dụng absurd.

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

12

Nói chung, bạn có thể sử dụng nó để tránh dường như-một phần mô hình phù hợp. Ví dụ, lấy một xấp xỉ của khai báo kiểu dữ liệu từ this answer:

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

Sau đó, bạn có thể sử dụng absurd như thế này, ví dụ:

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

Hãy xem xét biểu diễn này cho các thuật ngữ lambda được parametrized bởi các biến miễn phí của chúng. (Xem giấy tờ bằng Bellegarde và Hook 1994, Bird và Paterson 1999, Altenkirch và Reus 1999.)

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

Bạn có thể chắc chắn làm cho này một Functor, nắm bắt được khái niệm về đổi tên, và một Monad chụp khái niệm thay thế.

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

Bây giờ xem xét đóng ngữ: đây là những cư dân của Tm Void. Bạn có thể nhúng cụm từ đã đóng vào các cụm từ có các biến miễn phí tùy ý. Làm sao?

fmap absurd :: Tm Void -> Tm a 

Hàm bắt, tất nhiên, hàm này sẽ đi qua cụm từ không chính xác. Nhưng đó là một liên lạc trung thực hơn unsafeCoerce. Và đó là lý do tại sao vacuous đã được thêm vào Data.Void ...

Hoặc viết một bộ đánh giá. Dưới đây là các giá trị có biến miễn phí trong b.

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

Tôi vừa trình bày lambdas là bao đóng. Bộ đánh giá được parametrized bởi một môi trường lập bản đồ biến miễn phí trong a đến các giá trị trên 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 

Bạn đã đoán.Để đánh giá một thuật ngữ khép kín tại bất kỳ mục tiêu

eval absurd :: Tm Void -> Val b 

Tổng quát hơn, Void hiếm khi được sử dụng ngày của riêng mình, nhưng rất thuận tiện khi bạn muốn tạo một tham số kiểu theo một cách mà chỉ một số loại bất khả kháng (ví dụ ở đây , sử dụng biến miễn phí trong thuật ngữ đóng). Thông thường, các loại parametrized này đi kèm với các hàm bậc cao nâng các hoạt động trên các tham số cho các hoạt động trên toàn bộ loại (ví dụ: tại đây, fmap, >>=, eval). Vì vậy, bạn vượt qua absurd làm hoạt động chung trên Void.

Ví dụ khác, hãy tưởng tượng sử dụng Either e v để nắm bắt các tính toán hy vọng cung cấp cho bạn v nhưng có thể tăng ngoại lệ loại e. Bạn có thể sử dụng phương pháp này để ghi lại nguy cơ hành vi xấu một cách thống nhất. Đối với tính toán một cách hoàn hảo cư xử rất tốt trong bối cảnh này, hãy eVoid, sau đó sử dụng

either absurd id :: Either Void v -> v 

để chạy một cách an toàn hoặc

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

để nhúng các thành phần an toàn trong một thế giới không an toàn.

Ồ, và một lần cuối cùng, xử lý "không thể xảy ra". Nó xuất hiện trong việc xây dựng dây kéo chung, ở khắp mọi nơi mà con trỏ không thể.

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! 

Tôi quyết định không xóa phần còn lại, mặc dù nó không chính xác liên quan.

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) 

Thực ra, có thể nó có liên quan. Nếu bạn đang cảm thấy mạo hiểm, unfinished article này cho thấy làm thế nào để sử dụng Void để nén các đại diện của các điều khoản với các biến miễn phí

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

trong bất kỳ cú pháp tạo ra một cách tự do từ một DifferentiableTraversable functor f. Chúng tôi sử dụng Term f Void để đại diện cho các khu vực không có biến số miễn phí và [D f (Term f Void)] để đại diện cho ống đường hầm qua các khu vực mà không có biến miễn phí đến biến không bị cô lập hoặc đường giao nhau trong đường dẫn đến hai hoặc nhiều biến miễn phí. Phải kết thúc bài viết đó đôi khi.

Đối với loại không có giá trị (hoặc ít nhất, không có giá trị nói trong công ty lịch sự), Void hữu ích đáng kể. Và absurd là cách bạn sử dụng nó.

+0

Sẽ' forall f. bỏ trống f = unsafeCoerce f' là một quy tắc viết lại GHC hợp lệ? – Cactus

+1

@Cactus, không thực sự. Trường hợp của Bogus 'Functor' có thể là GADT mà không thực sự giống như các functors. – dfeuer

+0

Các 'Functor' có không phá vỡ quy tắc' fmap id = id' không? Hoặc là những gì bạn có nghĩa là "bogus" ở đây? – Cactus

10

Có nhiều cách khác nhau để đại diện cho the empty data type. Một là một kiểu dữ liệu đại số rỗng. Một cách khác là làm cho nó một bí danh cho ∀α.α hoặc

type Void' = forall a . a 

trong Haskell - đây là cách chúng tôi thể nó mã hóa trong hệ thống F (xem Chương 11 của Proofs and Types). Hai mô tả này tất nhiên là đẳng cấu và đẳng cấu được chứng kiến ​​bởi \x -> x :: (forall a.a) -> Void và bởi absurd :: Void -> a.

Trong một số trường hợp, chúng tôi thích các biến thể rõ ràng, thường nếu kiểu dữ liệu trống xuất hiện trong một cuộc tranh cãi của một chức năng, hoặc trong một kiểu dữ liệu phức tạp hơn, chẳng hạn như trong Data.Conduit:

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

Trong một số trường hợp, chúng tôi thích biến thể đa hình, thường là kiểu dữ liệu trống có liên quan đến kiểu trả về của hàm.

absurd phát sinh khi chúng tôi chuyển đổi giữa hai lần trình bày này.


Ví dụ: callcc :: ((a -> m b) -> m a) -> m a sử dụng (ẩn) forall b. Nó có thể là loại ((a -> m Void) -> m a) -> m a, bởi vì một cuộc gọi đến lục địa không thực sự trở lại, nó chuyển điều khiển đến một điểm khác. Nếu chúng ta muốn làm việc với continuations, ta có thể xác định

type Continuation r a = a -> Cont r Void 

(Chúng ta có thể sử dụng type Continuation' r a = forall b . a -> Cont r b nhưng điều đó sẽ đòi hỏi cấp bậc 2 kiểu.) Và sau đó, vacuousM chuyển đổi Cont r Void này vào Cont r b.

(Cũng lưu ý rằng bạn có thể sử dụng haskellers.com để tìm kiếm sử dụng (đảo ngược phụ thuộc) của một gói nhất định, muốn xem ai và làm thế nào sử dụng trống gói.)

+0

['TypeApplications'] (https://downloads.haskell.org/~ghc/master/users-guide/glasgow_exts.html#ghc-flag--XTypeApplications) có thể được sử dụng để rõ ràng hơn về các chi tiết của' bằng chứng: : (forall a. a) -> Void': 'bằng chứng fls = fls @ Void'. –

0

Trong các ngôn ngữ lệ thuộc, đánh máy như Idris , nó có thể hữu ích hơn Haskell. Thông thường, trong một hàm tổng khi bạn mẫu khớp với một giá trị thực sự không thể được đẩy vào hàm, bạn sẽ xây dựng một giá trị của kiểu không có người ở và sử dụng absurd để hoàn thành định nghĩa trường hợp.

Ví dụ chức năng này loại bỏ một phần tử từ một danh sách với các costraint loại cấp rằng nó có mặt tại đó:

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 

Trường hợp vụ án thứ hai được nói rằng có một yếu tố nào đó trong một danh sách rỗng, mà là, cũng vô lý. Nói chung, tuy nhiên, trình biên dịch không biết điều này và chúng ta thường phải rõ ràng. Sau đó, trình biên dịch có thể kiểm tra xem định nghĩa hàm không phải là một phần và chúng tôi có được các đảm bảo thời gian biên dịch mạnh hơn hay không.

Thông qua quan điểm Curry-Howard, ở đâu là các mệnh đề, sau đó absurd là loại QED trong bằng chứng là mâu thuẫn.

Các vấn đề liên quan