2017-05-22 20 views
8

Nó đã được tuyên bố rằng newtype T a = T (a -> Int) là một kiểu constructor mà không phải là một functor (nhưng là một fravtor contravariant). Làm thế nào? Hoặc những gì là contravariant functor (whence tôi giả định nó sẽ được rõ ràng lý do tại sao điều này không thể được thực hiện một functor bình thường)?Hiển thị rằng `newtype T a = T (a -> Int)` là một kiểu Constructor không phải là một Functor

Trả lời

6

Với

newtype T a = T (a -> Int) 

chúng ta hãy cố gắng xây dựng các ví dụ Contravariant cho datatype này.

Đây là typeclass trong câu hỏi:

class Contravariant f where 
    contramap :: (a -> b) -> f b -> f a 

Về cơ bản, contramap là giống như fmap, nhưng thay vì nâng một hàm a -> b-f a -> f b, nó nâng nó để f b -> f a.

Hãy bắt đầu bằng văn bản cho ví dụ ...

instance Contravariant T where 
    contramap g (T f) = ? 

Trước khi chúng tôi điền vào ?, chúng ta hãy suy nghĩ về những gì các loại gf là:

g :: a -> b 
f :: b -> Int 

Và cho rõ ràng, chúng tôi cũng có thể đề cập đến điều đó

f a ~ T (a -> Int) 
f b ~ T (b -> Int) 

Vì vậy, chúng ta có thể điền thông tin vào ? như sau:

instance Contravariant T where 
    contramap g (T f) = T (f . g) 

Để trở thành siêu pedantic, bạn có thể đổi tên g như aToB, và f như bToInt.

instance Contravariant T where 
    contramap aToB (T bToInt) = T (bToInt . aToB) 

Lý do bạn có thể viết một ví dụ Contravariant cho T a nắm để thực tế là a là ở vị trí contravariant trong T (a -> Int). Cách tốt nhất để thuyết phục bản thân rằng T a không phải là Functor là thử (và không thành công) để tự viết ví dụ Functor.

6

Giả sử T là một hàm. Sau đó, chúng tôi có

fmap :: (a -> b) -> T a -> T b 

Bây giờ, hãy thử triển khai.

instance Functor T where 
    fmap f (T g) = T $ \x -> _y 

Rõ ràng chúng ta bắt đầu với một cái gì đó như thế này, và kết hợp các giá trị f, g, và x bằng cách nào đó để tính toán giá trị cho y đó là của đúng loại. Làm thế nào chúng ta có thể làm điều đó? Vâng, thông báo tôi đã gọi nó là _y, mà nói với GHC tôi cần một số trợ giúp tìm ra những gì để đặt ở đó. GHC phải nói gì?

<interactive>:7:28: error: 
    • Found hole: _y :: Int 
     Or perhaps ‘_y’ is mis-spelled, or not in scope 
    • In the expression: _y 
     In the second argument of ‘($)’, namely ‘\ x -> _y’ 
     In the expression: T $ \ x -> _y 
    • Relevant bindings include 
     x :: b (bound at <interactive>:7:23) 
     g :: a -> Int (bound at <interactive>:7:13) 
     f :: a -> b (bound at <interactive>:7:8) 
     fmap :: (a -> b) -> T a -> T b (bound at <interactive>:7:3) 

Vì vậy, bây giờ chúng tôi đã rõ ràng về mọi loại có liên quan, phải không? Chúng tôi cần phải trả lại một Int bằng cách nào đó, và những gì chúng ta phải xây dựng nó ra khỏi là:

 x :: b 
     g :: a -> Int 
     f :: a -> b 

Vâng, được rồi, điều duy nhất chúng tôi có mà có thể có thể tạo ra một Intg, vì vậy hãy điền vào đó ở, rời g 's luận trống để hỏi GHC để được giúp đỡ nhiều hơn nữa:

instance Functor T where 
    fmap f (T g) = T $ \x -> g _y 

<interactive>:7:31: error: 
    • Found hole: _y :: a 
     Where: ‘a’ is a rigid type variable bound by 
       the type signature for: 
       fmap :: forall a b. (a -> b) -> T a -> T b 
       at <interactive>:7:3 
     Or perhaps ‘_y’ is mis-spelled, or not in scope 
    • In the first argument of ‘g’, namely ‘(_y)’ 
     In the expression: g (_y) 
     In the second argument of ‘($)’, namely ‘\ x -> g (_y)’ 
    • Relevant bindings include 
     x :: b (bound at <interactive>:7:23) 
     g :: a -> Int (bound at <interactive>:7:13) 
     f :: a -> b (bound at <interactive>:7:8) 
     fmap :: (a -> b) -> T a -> T b (bound at <interactive>:7:3) 

Được rồi, chúng ta có thể dự đoán này chính mình: gọi g, chúng ta cần một giá trị kiểu a từ đâu đó. Nhưng chúng tôi không có bất kỳ giá trị nào thuộc loại a trong phạm vi và chúng tôi cũng không có bất kỳ hàm nào trả về giá trị loại a! Chúng tôi đang mắc kẹt: không thể tạo ra giá trị của loại chúng tôi muốn bây giờ, mặc dù ở mỗi bước chúng tôi đã làm điều duy nhất có thể: không có gì chúng tôi có thể sao lưu và thử khác nhau.

Tại sao điều này lại xảy ra? Bởi vì nếu tôi cung cấp cho bạn chức năng loại a -> Int và nói "nhưng bằng cách này, đây là chức năng từ a -> b, vui lòng trả lại chức năng của tôi từ b -> Int", bạn không thể thực sự sử dụng chức năng từ a -> b, vì không ai bao giờ cung cấp cho bạn bất kỳ a s để gọi nó! Nếu tôi đã cho bạn một hàm từ b -> a thay vào đó, điều đó sẽ khá hữu ích, đúng không? Bạn có thể tạo ra một hàm từ b -> Int sau đó, trước tiên, hãy gọi hàm b -> a để nhận số a và sau đó gọi hàm a -> Int gốc để có được số Int mong muốn.

Và đó là điều mà một hàm giả thuyết về: chúng tôi đảo ngược mũi tên trong hàm được chuyển đến fmap, vì vậy nó có thể fmap trên những thứ bạn cần "(đối số chức năng) thay vì những thứ bạn" có "(giá trị cụ thể, trả lại giá trị của hàm, v.v.)


Bên cạnh đó, tôi đã tuyên bố trước đó rằng chúng tôi đã thực hiện "điều duy nhất" ở mỗi bước, đó là một chút của một lỗi. Chúng tôi không thể xây dựng một số Int trong số f, gx, nhưng tất nhiên chúng tôi có thể tạo tất cả các loại số ra khỏi không khí.Chúng tôi không biết gì về loại b, vì vậy chúng tôi không thể nhận được Int có nguồn gốc từ nó theo một cách nào đó, nhưng chúng tôi chỉ có thể nói "hãy luôn trả về số không" và kỹ thuật này đáp ứng trình kiểm tra loại:

instance Functor T where 
    fmap f (T g) = T $ const 0 

Rõ ràng điều này có vẻ khá sai, vì nó có vẻ như fg nên được khá quan trọng và chúng tôi đang bỏ qua chúng! Nhưng nó kiểm tra loại, vì vậy chúng tôi ổn, phải không?

Không, điều này vi phạm một trong những luật functor:

fmap id = id 

Chúng ta có thể chứng minh điều này một cách dễ dàng đủ:

fmap id (T $ const 5) = (T $ const 0) /= (T $ const 5) 

Và bây giờ chúng ta thực sự thử tất cả mọi thứ: cách duy nhất chúng ta có để xây dựng một số Int mà không sử dụng loại b của chúng tôi là loại bỏ mọi thứ, và tất cả những công dụng như vậy sẽ là đẳng cấu để sử dụng const, sẽ vi phạm luật Functor.

5

Đây là một góc nhìn khác. Như liminalisht đã cho thấy, TContravariant.Những gì chúng ta có thể nói về các loại được cả hai biến thể và contravariant?

import Data.Void 


change1, change1', change2 :: (Functor f, Contravariant f) => f a -> f b 
change1 = contramap (const()) . fmap (const()) 
change1' = (() >$) . (() <$) 
change2 = fmap absurd . contramap absurd 

Hai triển khai đầu tiên là về cơ bản giống nhau (change1' là một tối ưu hóa các change1); mỗi người trong số họ sử dụng thực tế là () là một "đối tượng đầu cuối" của Hask. change2 thay vì sử dụng thực tế là Void là một "đối tượng ban đầu".

Mỗi chức năng thay thế tất cả các a s trong f a với b s mà không biết bất cứ điều gì nào về a, b, hoặc mối quan hệ giữa chúng, để lại mọi thứ khác như vậy. Có thể rõ ràng rằng điều này có nghĩa là f a không thực sự phụ thuộc vào a. Tức là, tham số của f phải là ma. Đó là không phải là trường hợp cho T, do đó, nó cũng không thể là biến thể.

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