(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) và 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à fst
và snd
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
Đâ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ì. –
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