2011-08-28 26 views
24

Chức năng uncurry chỉ hoạt động cho các chức năng chụp hai đối số:Haskell có các hàm/bộ dữ liệu Vd không?

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

Nếu tôi muốn uncurry chức năng với một số tùy ý của lập luận, tôi chỉ có thể viết các chức năng riêng biệt:

uncurry2 f (a, b)   = f a b 
uncurry3 f (a, b, c)  = f a b c 
uncurry4 f (a, b, c, d) = f a b c d 
uncurry5 f (a, b, c, d, e) = f a b c d e 

Nhưng điều này được tẻ nhạt một cách nhanh chóng. Có cách nào để khái quát hóa điều này, vì vậy tôi chỉ phải viết một chức năng?

+0

Không theo cách đó, nhưng trong mọi trường hợp, tại sao bạn muốn thực hiện việc này? Theo kinh nghiệm của tôi, có rất ít trường hợp bạn cần nhiều hơn uncurry2 –

+7

@Paul: Không có lý do cụ thể, nhưng ngay sau khi tôi thấy bất kỳ kiểu lặp lại nào, tôi tự hỏi mình làm cách nào để khái quát và trừu tượng từ nó. – fredoverflow

Trả lời

23

Hãy thử uncurryN từ gói tuple. Giống như tất cả các dạng quá tải, nó được thực hiện bằng cách sử dụng các lớp kiểu. Trong trường hợp này bằng cách đánh vần theo cách thủ công các trường hợp tối đa 15 tuple, điều này phải là quá đủ.

Chức năng biến thể cũng có thể sử dụng các loại lớp. Một ví dụ về điều này là Text.Printf. Trong trường hợp này, nó được thực hiện bằng cảm ứng cấu trúc trên loại chức năng. Giản, nó hoạt động như thế này:

class Foo t 

instance Foo (IO a) 
instance Foo b => Foo (a -> b) 

foo :: Foo 

Nó không nên khó để thấy rằng foo có thể được khởi tạo cho các loại IO a, a -> IO b, a -> b -> IO c và vân vân. QuickCheck cũng sử dụng kỹ thuật này.

cấu cảm ứng sẽ không hoạt động trên các bộ, mặc dù, như là một -tuple n là hoàn toàn không liên quan đến một -tuple n + 1, vì vậy đó là lý do tại sao các trường hợp cần phải được nêu ra bằng tay.

+5

Ngạc nhiên thay, nguồn của 'Data.Tuple.Curry' đề cập đến các trường hợp khác nhau đã được tạo ra bởi một chương trình riêng biệt, và có lẽ được dán vào như thể chúng đã được gõ bằng tay. –

2

Không có cách nào đơn giản để viết một định nghĩa duy nhất là uncurry sẽ hoạt động với các số đối số khác nhau.

Tuy nhiên, có thể sử dụng Template Haskell để tạo nhiều biến thể khác nhau mà bạn sẽ phải viết bằng tay.

+0

Bạn có thể viết một câu chuyện n-ary 'uncurry' với Daniel Fridlender và mẫu của Mia Indrika cho các chức năng gia đình đáng ghét. Ví dụ về 'zipWithN' được đưa ra trong bài báo" Chúng ta có cần các kiểu phụ thuộc không? ". –

+0

"Không có cách nào" có lẽ là một chút táo bạo. Tôi đã thay đổi câu trả lời của mình thành "không có cách đơn giản". –

+0

@stephentetley Việc triển khai đó có được triển khai trong gói ở bất kỳ đâu không? – CMCDragonkai

11

Tìm cách để giả mạo loại điều này bằng cách sử dụng thủ thuật hệ thống loại quá hạn là một trong những sở thích của tôi, vì vậy hãy tin tôi khi tôi nói rằng kết quả là khá xấu xí. Đặc biệt, lưu ý rằng các bộ dữ liệu không được định nghĩa đệ quy, vì vậy không có cách nào thực sự để tóm tắt trực tiếp chúng; theo như hệ thống kiểu của Haskell có liên quan, mỗi kích thước tuple là hoàn toàn khác biệt.

Bất kỳ phương pháp khả thi nào để làm việc với bộ dữ liệu trực tiếp do đó sẽ yêu cầu tạo mã - sử dụng TH hoặc công cụ bên ngoài như gói tuple.

Để giả mạo nó mà không sử dụng mã được tạo, trước tiên bạn phải sử dụng định nghĩa đệ quy - thường là cặp lồng nhau có giá trị "không" để đánh dấu kết thúc là (,)() hoặc gì đó tương đương. Bạn có thể nhận thấy rằng điều này tương tự như định nghĩa của các danh sách theo điều khoản của (:)[] - và trên thực tế, các phân đoạn giả định được sắp xếp theo kiểu đệ quy có thể được xem là cấu trúc dữ liệu cấp loại (danh sách các loại) hoặc danh sách không đồng nhất (ví dụ, HList hoạt động theo cách này).

Những nhược điểm bao gồm, nhưng không giới hạn, thực tế là sử dụng mọi thứ được xây dựng theo cách này có thể khó xử hơn. và kết quả cuối cùng là không nhất thiết phải tương đương anyway - có nhiều sự khác biệt nontrivial giữa (a, (b, (c,())))(a, b, c), ví dụ.

Nếu bạn muốn xem mức độ khủng khiếp của nó, bạn có thể xem the stuff I have on GitHub, đặc biệt là các bit here.

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