11
  1. Làm thế nào điều này có thể, những gì đang diễn ra ở đó?Trong Haskell, + là một hàm, (+ 2) là một hàm, (+ 2 3) là 5. Chính xác những gì đang diễn ra ở đó?

  2. Có tên này không?

  3. Ngôn ngữ nào khác có cùng hành vi này?

  4. Bất kỳ không có hệ thống đánh máy mạnh?

+2

Nhận sách giáo khoa/hướng dẫn chuẩn cho Haskell? – ShiDoiSi

+1

Làm cách nào có thể? –

+6

Những cái tên bạn đang tìm kiếm là currying và một phần ứng dụng. –

Trả lời

38

Trên thực tế (+ 2 3) là lỗi loại. (+) 2 3 hoặc (+ 2) 3 sẽ cung cấp cho bạn 5.

Hành vi này thực sự đơn giản và trực quan nếu bạn xem các loại. Để tránh các biến chứng của các toán tử infix như +, thay vào đó, tôi sẽ sử dụng hàm plus. Tôi cũng sẽ chuyên về plus để chỉ hoạt động trên Int, để giảm nhiễu đường typeclass.

Giả sử chúng tôi có chức năng plus, thuộc loại Int -> Int -> Int. Một cách để đọc đó là "một hàm của hai số Int s trả về một số Int". Nhưng ký hiệu đó hơi vụng về khi đọc, phải không? Kiểu trả về không được chọn ra đặc biệt ở bất cứ đâu. Tại sao chúng ta viết chữ ký kiểu hàm theo cách này? Bởi vì -> là liên kết phù hợp, một loại tương đương sẽ là Int -> (Int -> Int). Điều này có vẻ giống như nó nói "một hàm từ một số Int đến (một hàm từ một số Int đến một số Int)". Nhưng hai loại thực tế đó giống hệt nhau, và cách giải thích thứ hai là chìa khóa để hiểu cách hoạt động của hành vi này.

Haskell xem tất cả các chức năng từ một đối số đến một kết quả duy nhất. Có thể có các tính toán bạn cần lưu ý khi kết quả của phép tính phụ thuộc vào hai hoặc nhiều đầu vào (chẳng hạn như plus).Haskell nói rằng hàm plus là một hàm lấy một đầu vào đơn lẻ và tạo ra một đầu ra là một hàm khác. Hàm thứ hai này lấy một đầu vào đơn và tạo ra một đầu ra là một số. Bởi vì hàm thứ hai được tính toán đầu tiên (và sẽ khác với đầu vào khác nhau cho hàm đầu tiên), đầu ra "cuối cùng" có thể phụ thuộc vào cả hai đầu vào, vì vậy chúng ta có thể thực hiện các tính toán với nhiều đầu vào. đầu vào đơn.

Tôi đã hứa điều này sẽ rất dễ hiểu nếu bạn xem các loại. Dưới đây là một số biểu thức Ví dụ với các loại họ chú thích một cách rõ ràng:

plus  :: Int -> Int -> Int 
plus 2  ::  Int -> Int 
plus 2 3 ::    Int 

Nếu một cái gì đó là một chức năng và bạn áp dụng nó vào một cuộc tranh cãi, để có được những loại kết quả của ứng dụng mà tất cả các bạn cần làm là loại bỏ tất cả mọi thứ lên đến mũi tên đầu tiên từ loại hàm. Nếu điều đó để lại một loại có nhiều mũi tên hơn, thì bạn vẫn có một chức năng! Khi bạn thêm đối số bên phải của một biểu thức, bạn loại bỏ các loại tham số từ bên trái của loại của nó. Loại làm cho nó ngay lập tức rõ ràng loại của tất cả các kết quả trung gian là gì, và tại sao plus 2 là một hàm có thể được áp dụng thêm (loại của nó có một mũi tên) và plus 2 3 không phải là (kiểu của nó không có mũi tên).

"Currying" là quá trình chuyển một hàm của hai đối số thành hàm của một đối số trả về hàm của đối số khác trả về bất kỳ hàm ban đầu nào được trả về. Nó cũng được sử dụng để chỉ tài sản của các ngôn ngữ như Haskell tự động có tất cả các chức năng hoạt động theo cách này; mọi người sẽ nói rằng Haskell "là một ngôn ngữ bị quấy rầy" hoặc "có currying", hoặc "có chức năng curried".

Lưu ý rằng điều này hoạt động đặc biệt thanh lịch vì cú pháp của Haskell cho ứng dụng hàm là sự kề cận đơn giản. Bạn được tự do đọc plus 2 3 khi áp dụng plus đến 2 đối số hoặc áp dụng plus đến 2 và sau đó áp dụng kết quả cho 3; bạn có thể mô hình tinh thần theo cách nào phù hợp nhất với những gì bạn đang làm vào thời điểm đó.

Trong các ngôn ngữ có ứng dụng chức năng giống như C bằng danh sách đối số được lồng ghép, điều này sẽ bị hỏng một chút. plus(2, 3) rất khác với plus(2)(3) và bằng các ngôn ngữ có cú pháp này, hai phiên bản plus có thể có các loại khác nhau. Vì vậy, ngôn ngữ với loại cú pháp có xu hướng không có tất cả các chức năng được curried tất cả các thời gian, hoặc thậm chí để có currying tự động của bất kỳ chức năng mà bạn thích. Nhưng các ngôn ngữ như vậy có lịch sử cũng có xu hướng không có chức năng như các giá trị lớp học đầu tiên, mà làm cho việc thiếu currying một điểm tranh luận.

+2

+1 Giải thích tuyệt vời. Đã yêu nó. – Nawaz

+0

Giải thích tốt, mặc dù tất nhiên ví dụ của bạn về 'cộng (2, 3)' và 'cộng (2) (3)' và chúng có các kiểu khác nhau cũng áp dụng cho Haskell, không chỉ là "ngôn ngữ với ứng dụng chức năng giống như C". 'plus :: (Int, Int) -> Int' cho phép' cộng (2, 3) 'và' cộng :: Int -> Int -> Int' cho phép 'cộng (2) (3)' –

+0

@Bertie Vâng vâng , mặc dù không phải trong số đó là một cách thành ngữ để viết ứng dụng trong Haskell. Và 'cộng (2, 3)' trong Haskell đang áp dụng một hàm cho một đối số duy nhất là một bộ dữ liệu; xuất hiện trong cú pháp C giống như 'plus ((2, 3))'. Tất nhiên bạn có thể xem các hàm lấy một bộ dữ liệu như mã hóa các hàm đa đối số (giống như hàm trả về là một mã hóa các hàm đa đối số), nhưng tôi thích nghĩ riêng về chúng. – Ben

6

Trong Haskell, bạn có thể thực hiện chức năng của hai đối số, áp dụng nó cho một đối số và nhận hàm của một đối số. Trong thực tế, nói đúng, + không phải là một hàm của hai đối số, nó là một hàm của một đối số trả về một hàm của một đối số.

2
  1. Về layman, + là chức năng thực tế và nó đang chờ để nhận một số lượng nhất định các thông số (trong trường hợp này 2 hoặc nhiều hơn) cho đến khi nó trả về. Nếu bạn không cho nó hai hoặc nhiều tham số, thì nó sẽ vẫn là một hàm chờ một tham số khác.
  2. Nó được gọi là Currying
  3. Rất nhiều ngôn ngữ chức năng (Scala, Đề án, vv)
  4. ngôn ngữ chức năng Đa số được mạnh mẽ gõ, nhưng điều này là tốt cuối cùng vì nó làm giảm sai sót, trong đó hoạt động tốt trong doanh nghiệp hoặc các hệ thống quan trọng.

Một lưu ý phụ, ngôn ngữ Haskell được đặt tên theo Haskell Curry, người đã khám phá lại hiện tượng Currying chức năng trong khi làm việc trên logic kết hợp.

8

Trong Haskell, tất cả các chức năng lấy chính xác 1 đầu vào và tạo chính xác 1 đầu ra. Đôi khi, đầu vào hoặc đầu ra của một hàm có thể là một hàm khác. Đầu vào hoặc đầu ra của một hàm cũng có thể là một bộ tuple.Bạn có thể mô phỏng một chức năng với nhiều đầu vào theo một trong hai cách sau:

  • Sử dụng một tuple như là đầu vào
    (in1, in2) -> out

  • Sử dụng một chức năng như đầu ra *
    in1 -> (in2 -> out)

Tương tự, bạn có thể mô phỏng một hàm có nhiều kết quả đầu ra theo một trong hai cách:

  • Sử dụng một tuple như đầu ra *
    in -> (out1, out2)

  • Sử dụng một hàm như là một "đầu vào thứ hai" (một chức năng như--đầu ra la)
    in -> ((out1 -> (out2 -> a)) -> a)

* này cách thường được ưa chuộng bởi Haskellers

Chức năng (+) mô phỏng lấy 2 đầu vào theo cách Haskell điển hình để tạo ra một hàm làm đầu ra. (Chuyên để Int để dễ liên lạc :)
(+) :: Int -> (Int -> Int)

Vì lợi ích của sự thuận tiện, -> là phải kết hợp, vì vậy các loại chữ ký cho (+) cũng có thể được viết
(+) :: Int -> Int -> Int


(+) là hàm nhận một số và tạo hàm khác từ số này sang số khác.

(+) 5 là kết quả của việc áp dụng (+) cho đối số 5, do đó, đây là hàm từ số này sang số khác.

(5 +) là một cách khác để viết (+) 5

2 + 3 là một cách khác để viết (+) 2 3. Ứng dụng chức năng là kết hợp trái, vì vậy đây là một cách khác để viết (((+) 2) 3). Nói cách khác: Áp dụng hàm (+) cho đầu vào 2. Kết quả sẽ là một hàm. Hãy rằng chức năng và áp dụng cho đầu vào 3. Kết quả của rằng là một số.

Do đó, (+) là một hàm, (5 +) là một hàm và (+) 2 3 là một số.