2013-04-13 35 views
12

Tôi đang làm một số excersises nơi tôi có thêm một loại chức năng và giải thích những gì nó làm. Tôi bị kẹt với điều này:

phy = uncurry ($) 

Loại, theo GHCi là phy :: (a -> b, a) -> b. Kiến thức haskell của tôi là cơ bản nên tôi thực sự không biết nó làm gì.

+0

Đây là một ý nghĩ dễ thương: 'uncurry ($) 'thực sự giống như' uncurry id'! Điều này xảy ra vì '($)' thực sự chỉ là 'id' chuyên về các hàm. Hãy thử ': t uncurry id' để xem ý tôi là gì. –

+4

Mã này minh họa sức mạnh của suy nghĩ theo loại. Tìm ra những gì 'uncurry ($) 'có một chút suy nghĩ, nhưng nó phải được rõ ràng phải tắt những gì một chức năng của loại' (a -> b, a) -> b' nào. Trên thực tế, chỉ có một thứ có chức năng với loại * có thể * làm. Bạn nên phát triển trực giác này một cách tự nhiên nếu bạn chú ý đến các loại khi bạn lập trình trong Haskell. Nếu sau này bạn muốn chính thức hóa trực giác của mình, hãy thử bài báo "[Theorems for free] (http://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf)" [PDF] của Philip Wadler – pash

Trả lời

9
uncurry :: (a -> b -> c) -> (a, b) -> c 

($) :: (a -> b) -> a -> b 

uncurry ($) :: (a -> b, a) -> b 

Nếu bạn kiểm tra các loại uncurry$ và mô tả nó:

uncurry chuyển đổi một hàm cà ri với một chức năng trên cặp.

Tất cả phải thực hiện chức năng (a -> b -> c) và trả về hàm lấy tham số làm bộ tuple.

Vì vậy phy làm điều tương tự như $, nhưng thay vì f $ x hoặc ($) f x bạn gọi nó như phy (f, x).

+0

Tôi nhận được bây giờ, cảm ơn. – user2278354

+0

Tôi nghĩ rằng nó sẽ còn rõ ràng hơn nếu bạn sử dụng các biến kiểu giống nhau trong chữ ký kiểu cho 'uncurry ($)'. –

+0

bạn nói đúng, tôi vừa sao chép từ ghci – Arjan

12

Hãy đánh vần phần kiểu hệ thống. Chúng tôi sẽ bắt đầu với các loại uncurry($):

uncurry :: (a -> b -> c) -> (a, b) -> c 
($)  :: (a -> b) -> a -> b 

Kể từ khi biểu hiện mục tiêu có ($) như là đối số của uncurry, chúng ta hãy lên đường loại của họ để phản ánh điều này:

uncurry :: (a  -> b -> c) -> (a, b) -> c 
($)  :: (a -> b) -> a -> b 

Toàn bộ loại của ($) xếp hàng với loại đối số đầu tiên là uncurry và đối số và loại kết quả của số ($) phù hợp với đối số đầu tiên của uncurry như được hiển thị. Đây là sự tương ứng:

uncurry's a <==> ($)'s a -> b 
uncurry's b <==> ($)'s a 
uncurry's c <==> ($)'s b 

Đây là kinda khó hiểu, bởi vì ab loại biến trong một loại không giống nhau như trong khác (giống như x trong plusTwo x = x + 2 là không giống như các x trong timesTwo x = x * 2). Nhưng chúng ta có thể viết lại các loại để giúp lý do về điều này. Trong các ký hiệu kiểu chữ Haskell đơn giản như thế này, bất cứ khi nào bạn nhìn thấy một biến kiểu, bạn có thể thay thế tất cả các lần xuất hiện của nó bằng bất kỳ kiểu nào khác cũng nhận được một kiểu hợp lệ. Nếu bạn chọn các biến loại mới (nhập các biến không xuất hiện ở bất kỳ vị trí nào trong bản gốc), bạn sẽ nhận được loại tương đương (một biến có thể được chuyển đổi về nguyên bản); nếu bạn chọn loại không tươi, bạn nhận được phiên bản chuyên biệt của bản gốc hoạt động với phạm vi loại hẹp hơn.

Nhưng dù sao, chúng ta hãy áp dụng điều này để loại uncurry ::

-- Substitute a ==> x, b ==> y, c ==> z: 
uncurry :: (x -> y -> z) -> (x, y) -> z 

Hãy làm lại "xếp hàng" bằng cách sử dụng kiểu viết lại:

uncurry :: (x  -> y -> z) -> (x, y) -> z 
($)  :: (a -> b) -> a -> b 

Bây giờ là rõ ràng: x <==> a -> b, y <==> az <==> b.Bây giờ, thay thế các biến kiểu uncurry 's với nhiều loại đối tác của họ trong ($), chúng tôi nhận được:

uncurry :: ((a -> b) -> a -> b) -> (a -> b, a) -> b 
($)  :: (a -> b) -> a -> b 

Và cuối cùng:

uncurry ($) :: (a -> b, a) -> b 

Vì vậy, đó là cách bạn hình dung ra kiểu. Làm thế nào về những gì nó? Vâng, cách tốt nhất để làm điều đó trong trường hợp này là để xem xét các loại và suy nghĩ về nó một cách cẩn thận, tìm ra những gì chúng ta sẽ phải viết để có được một chức năng của loại đó. Hãy viết lại nó theo cách này để làm cho nó bí ẩn hơn:

mystery :: (a -> b, a) -> b 
mystery = ... 

Vì chúng ta biết mystery là một chức năng của một đối số, chúng ta có thể mở rộng định nghĩa này để phản ánh rằng:

mystery x = ... 

Chúng tôi cũng biết rằng nó đối số là một cặp, vì vậy chúng tôi có thể mở rộng hơn một chút:

mystery (x, y) = ... 

Kể từ khi chúng ta biết rằng x là một chức năng và y :: a, tôi LDS e sử dụng f có nghĩa là "chức năng" và đặt tên biến giống như họ type-nó giúp tôi lý do về chức năng, vì vậy hãy làm điều đó:

mystery (f, a) = ... 

Bây giờ, điều gì làm chúng ta đặt ở phía bên tay phải ? Chúng tôi biết nó phải thuộc loại b, nhưng chúng tôi không biết loại b là gì (nó thực sự là bất cứ điều gì người gọi chọn, vì vậy chúng tôi không thể biết). Vì vậy, chúng tôi phải bằng cách nào đó thực hiện một b bằng cách sử dụng chức năng của chúng tôi f :: a -> b và giá trị a :: a. Aha! Chúng tôi chỉ có thể gọi hàm với giá trị:

mystery (f, a) = f a 

Chúng tôi đã viết chức năng này mà không nhìn vào uncurry ($), nhưng nó chỉ ra rằng nó làm điều tương tự như uncurry ($) có, và chúng ta có thể chứng minh điều đó. Hãy bắt đầu với các định nghĩa của uncurry($):

uncurry f (a, b) = f a b 
f $ a = f a 

Bây giờ, thay thế bằng cho equals:

uncurry ($) (f, a) = ($) f a   -- definition of uncurry, left to right 
        = f $ a   -- Haskell syntax rule 
        = f a    -- definition of ($), left to right 
        = mystery (f, a) -- definition of mystery, right to left 

Vì vậy, một trong những cách để tấn công một loại mà bạn không hiểu tại Haskell là chỉ cần cố gắng và viết một số mã có loại đó. Haskell là khác nhau từ các ngôn ngữ khác trong đó rất thường xuyên này là một chiến lược tốt hơn so với cố gắng để đọc mã.

4

Hai câu trả lời còn lại là tốt. Tôi chỉ có một chút khác biệt trên đó.

uncurry :: (a -> b -> c) -> (a, b) -> c 
($)  :: (a -> b) -> a -> b 

Kể từ khi "->" trong loại chữ ký đối tác sang bên phải, tôi tương đương có thể viết hai chữ ký kiểu như thế này:

uncurry :: (a -> b -> c) -> ((a, b) -> c) 
($)  :: (a -> b) -> (a -> b) 

uncurry mất một chức năng độc đoán của hai đầu vào và thay đổi nó vào một funciton của một đối số trong đó đối số là một bộ của hai đối số ban đầu.

($) có chức năng một đối số đơn giản và biến nó thành ... chính nó. Hiệu ứng duy nhất của nó là cú pháp. f $ tương đương với f.

2

(Hãy chắc chắn rằng bạn hiểu chức năng bậc cao và currying, đọc Tìm hiểu Bạn một chương Haskell trên higher-order functions, sau đó đọc difference between . (dot) and $ (dollar sign)function composition (.) and function application ($) idioms)

($) chỉ là một ứng dụng chức năng, f $ x tương đương với f x. Nhưng đó là tốt, bởi vì chúng ta có thể sử dụng ứng dụng chức năng rõ ràng, ví dụ:

map ($2) $ map ($3) [(+), (-), (*), (**)] -- returns [5.0,1.0,6.0,9.0] 

tương đương với:

map (($2) . ($3)) [(+), (-), (*), (**)] -- returns [5.0,1.0,6.0,9.0] 

Kiểm tra các loại ($): ($) :: (a -> b) -> a -> b. Bạn biết rằng các khai báo kiểu là đúng liên kết, do đó loại ($) cũng có thể được viết là (a -> b) -> (a -> b). Đợi một chút, đó là gì? Một hàm nhận hàm unary và trả về một hàm unary cùng loại? Điều này trông giống như phiên bản cụ thể của chức năng nhận dạng id :: a -> a. Ok, một số loại đầu tiên:

($) :: (a -> b) -> a -> b 
id :: a -> a 
uncurry :: (a -> b -> c) -> (a, b) -> c 
uncurry ($) :: (b -> c, b) -> c 
uncurry id :: (b -> c, b) -> c 

Khi mã hóa Haskell, luôn xem xét các loại, chúng cung cấp cho bạn nhiều thông tin trước khi bạn xem mã. Vì vậy, ($) là gì? Đó là một chức năng của 2 đối số. uncurry là gì? Đó cũng là một hàm của 2 đối số, đầu tiên là hàm của 2 đối số. Vì vậy, uncurry ($) nên đánh máy, vì 1 đối số của uncurry phải là một hàm của 2 đối số, trong đó ($) là. Bây giờ, hãy thử đoán loại uncurry ($). Nếu loại ($) 's là (a -> b) -> a -> b, thay thế nó cho (a -> b -> c): a trở thành (a -> b), b trở thành a, c trở thành b Do đó, uncurry ($) trả về một chức năng của loại ((a -> b), a) -> b. Hoặc (b -> c, b) -> c như trên, điều tương tự. Vì vậy, loại đó cho chúng ta biết điều gì? uncurry ($) chấp nhận một tuple (function, value). Bây giờ hãy thử đoán nó làm gì từ loại một mình.

Bây giờ, trước câu trả lời, một sự xen kẽ. Haskell là như vậy strongly typed, rằng nó cấm trả về một giá trị của một loại cụ thể, nếu khai báo kiểu có một biến kiểu như là một kiểu giá trị trả về. Vì vậy, nếu bạn có chức năng với loại a -> b, bạn không thể trả lại String. Điều này có ý nghĩa, bởi vì nếu loại chức năng của bạn là a -> a và bạn luôn trả về String, người dùng sẽ có thể chuyển giá trị của bất kỳ loại nào khác? Bạn phải có loại String -> String hoặc có loại a -> a và trả lại giá trị chỉ phụ thuộc vào biến đầu vào. Nhưng hạn chế này cũng có nghĩa là không thể viết một hàm cho một số kiểu nhất định. Không có hàm nào với loại a -> b, bởi vì không ai biết, loại bê tông nào nên thay vì b. Hoặc [a] -> a, bạn biết rằng hàm này không thể là total, bởi vì người dùng có thể chuyển danh sách trống và hàm sẽ trả về trong trường hợp đó? Loại a nên phụ thuộc vào một loại bên trong danh sách, nhưng danh sách không có "bên trong", trống của nó, vì vậy bạn không biết loại yếu tố bên trong danh sách trống là gì.Hạn chế này chỉ cho phép một khuỷu tay rất hẹp cho các chức năng có thể theo một loại nhất định, và đây là lý do tại sao bạn nhận được rất nhiều thông tin về hành vi có thể của một hàm chỉ bằng cách đọc kiểu.

uncurry ($) trả về kiểu c, nhưng đó là biến loại, không phải là loại cụ thể, do đó giá trị của nó phụ thuộc vào loại nào cũng thuộc loại c. Và chúng ta thấy từ kiểu khai báo rằng hàm trong tuple trả về các giá trị kiểu c. Và cùng một chức năng yêu cầu một giá trị kiểu b, mà chỉ có thể được tìm thấy trong cùng một bộ dữ liệu. Không có loại bê tông cũng không typeclasses, vì vậy điều duy nhất uncurry ($) có thể làm là để có những snd của một tuple, đặt nó như là một cuộc tranh cãi trong chức năng trong fst của một tuple, trở lại bất cứ điều gì nó sẽ trả về:

uncurry ($) ((+2), 2) -- 4 
uncurry ($) (head, [1,2,3]) -- 1 
uncurry ($) (map (+1), [1,2,3]) -- [2,3,4] 

Có là một chương trình dễ thương djinn tạo các chương trình Haskell dựa trên các loại. Chơi với nó để thấy rằng dự đoán loại của chúng ta về chức năng uncurry ($) 's là đúng:

Djinn> f ? a -> a 
f :: a -> a 
f a = a 
Djinn> f ? a -> b 
-- f cannot be realized. 
Djinn> f ? (b -> c, b) -> c 
f :: (b -> c, b) -> c 
f (a, b) = a b 

Điều này cho thấy, cũng có, mà fstsnd là chức năng duy nhất có thể có các loại tương ứng của họ:

Djinn> f ? (a, b) -> a 
f :: (a, b) -> a 
f (a, _) = a 
Djinn> f ? (a, b) -> b 
f :: (a, b) -> b 
f (_, a) = a 
Các vấn đề liên quan