2010-09-16 37 views
10

Tôi thấy mình gặp sự cố thường gặp khi viết các chương trình lớn hơn trong Haskell. Tôi thấy mình thường muốn nhiều loại riêng biệt chia sẻ một đại diện nội bộ và một số hoạt động cốt lõi.Xử lý nhiều loại với cùng một biểu diễn nội bộ và bản mẫu nhỏ nhất?

Có hai cách tiếp cận tương đối rõ ràng để giải quyết vấn đề này.

Một người đang sử dụng loại lớp và tiện ích mở rộng GeneralizedNewtypeDeriving. Đặt đủ logic vào một loại lớp để hỗ trợ các hoạt động được chia sẻ mà trường hợp sử dụng mong muốn. Tạo một kiểu với biểu diễn mong muốn và tạo một thể hiện của lớp kiểu cho kiểu đó. Sau đó, đối với từng trường hợp sử dụng, tạo trình bao bọc cho nó bằng newtype và lấy được lớp chung.

Cách khác là khai báo loại có biến kiểu ảo và sau đó sử dụng EmptyDataDecls để tạo các loại riêng biệt cho từng trường hợp sử dụng khác nhau.

Mối quan tâm chính của tôi là không trộn lẫn các giá trị chia sẻ nội dung đại diện và hoạt động, nhưng có ý nghĩa khác nhau trong mã của tôi. Cả hai cách tiếp cận này giải quyết vấn đề đó, nhưng cảm thấy vụng về đáng kể. Mối quan tâm thứ hai của tôi là giảm số lượng boilerplate cần thiết, và cả hai cách tiếp cận đều làm tốt ở đó.

Những ưu điểm và nhược điểm của từng phương pháp tiếp cận là gì? Có một kỹ thuật đến gần hơn để làm những gì tôi muốn, cung cấp loại an toàn mà không có mã boilerplate?

Trả lời

2

Tôi đã đánh giá các ví dụ đồ chơi và không tìm thấy sự khác biệt hiệu suất giữa hai cách tiếp cận, nhưng việc sử dụng thường khác một chút. Ví dụ:

Ví dụ: trong một số trường hợp, bạn có loại chung có nhà thầu được hiển thị và bạn muốn sử dụng trình bao bọc newtype để chỉ ra loại ngữ nghĩa cụ thể hơn. Sử dụng newtype s sau đó dẫn đến các trang web như gọi,

s1 = Specific1 $ General "Bob" 23 
s2 = Specific2 $ General "Joe" 19 

Trường hợp thực tế là cơ quan đại diện nội bộ đều giống nhau giữa newtypes cụ thể khác nhau trong suốt.

Thẻ phương pháp loại hầu như luôn luôn đi cùng với đại diện constructor ẩn,

data General2 a = General2 String Int 

và việc sử dụng các nhà xây dựng thông minh, dẫn đến một định nghĩa kiểu dữ liệu và gọi các trang web như,

mkSpecific1 "Bob" 23 

Phần lý do là bạn muốn một số cách ánh sáng cú pháp chỉ ra thẻ bạn muốn. Nếu bạn không cung cấp các nhà xây dựng thông minh, thì mã máy khách thường sẽ nhận các chú thích loại để thu hẹp mọi thứ, ví dụ:,

myValue = General2 String Int :: General2 Specific1 

Khi bạn áp dụng các nhà thầu thông minh, bạn có thể dễ dàng thêm logic xác thực bổ sung để hiểu sai thẻ. Một khía cạnh tốt đẹp của phương pháp tiếp cận kiểu ảo là khớp mẫu không thay đổi chút nào đối với mã nội bộ có quyền truy cập vào biểu diễn.

internalFun :: General2 a -> General2 a -> Int 
internalFun (General2 _ age1) (General2 _ age2) = age1 + age2 

Tất nhiên bạn có thể sử dụng newtype s với nhà thầu thông minh và một lớp học nội để truy cập vào các đại diện chia sẻ, nhưng tôi nghĩ rằng một điểm quyết định quan trọng trong không gian thiết kế này là cho dù bạn muốn giữ lại nhà xây dựng đại diện của bạn tiếp xúc. Nếu việc chia sẻ biểu diễn phải trong suốt và mã của khách hàng sẽ được tự do sử dụng bất kỳ thẻ nào mà nó muốn mà không có xác thực bổ sung, thì trình bao bọc newtype với GeneralizedNewtypeDeriving hoạt động tốt. Nhưng nếu bạn định áp dụng các nhà xây dựng thông minh để làm việc với các biểu diễn mờ đục, thì tôi thường thích các kiểu phantom.

+0

Nếu bộ nhớ phục vụ cho tôi, 'dữ liệu Foo a = Foo a',' dữ liệu Foo ab = Foo a' và 'newtype Bar a = Thanh (Foo a)' (với 'Foo' đầu tiên) nên biên dịch thành đại diện thời gian chạy, do đó, việc tìm kiếm sự khác biệt không nhỏ trong hiệu suất sẽ hơi bất ngờ. –

+1

@camccann Vẻ đẹp của ghc-core và Tiêu chí là bằng chứng thực nghiệm để bổ sung bộ nhớ! :) Tôi nghĩ rằng câu hỏi hiệu suất có nhiều hơn để làm với liệu các hoạt động đến từ một lớp tác động đến hiệu suất của họ như trái ngược với đại diện thời gian chạy của bản thân giá trị. Các hàm đa hình đi từ '(General a, General b) => a -> b -> Int' thành' General2 a -> General2 b -> Int'. – Anthony

3

Có một cách tiếp cận đơn giản khác.

data MyGenType = Foo | Bar 

op :: MyGenType -> MyGenType 
op x = ... 

op2 :: MyGenType -> MyGenType -> MyGenType 
op2 x y = ... 

newtype MySpecialType {unMySpecial :: MyGenType} 

inMySpecial f = MySpecialType . f . unMySpecial 
inMySpecial2 f x y = ... 

somefun = ... inMySpecial op x ... 
someOtherFun = ... inMySpecial2 op2 x y ... 

Cách khác,

newtype MySpecial a = MySpecial a 
instance Functor MySpecial where... 
instance Applicative MySpecial where... 

somefun = ... fmap op x ... 
someOtherFun = ... liftA2 op2 x y ... 

Tôi nghĩ rằng các phương pháp này là đẹp hơn nếu bạn muốn sử dụng loại nói chung của bạn "khỏa thân" với bất kỳ tần số, và chỉ thỉnh thoảng muốn gắn thẻ nó. Nếu, mặt khác, bạn thường muốn sử dụng nó được gắn thẻ, sau đó cách tiếp cận loại ma hơn trực tiếp thể hiện những gì bạn muốn.

+0

Thực sự thích phím shift hôm nay, phải không? –

+0

Rất tiếc. Định dạng cố định. – sclv

+1

btw, bạn có thể tự động tạo trongMySpecial1,2,3 ../ withMySpecial etc với mẫu haskell. xem http://github.com/yairchu/peakachu/blob/master/src/Data/Newtype.hs – yairchu

1

Đặt đủ logic vào một loại lớp để hỗ trợ các hoạt động được chia sẻ mà trường hợp sử dụng mong muốn. Tạo một kiểu với biểu diễn mong muốn và tạo một thể hiện của lớp kiểu cho kiểu đó. Sau đó, đối với từng trường hợp sử dụng, tạo trình bao bọc cho nó bằng newtype và lấy được lớp chung.

Điều này trình bày một số cạm bẫy, tùy thuộc vào bản chất của loại và loại hoạt động liên quan. Đầu tiên, nó buộc nhiều chức năng không nhất thiết phải đa hình - ngay cả khi trong thực tế mọi trường hợp cũng làm điều tương tự cho các trình bao bọc khác nhau, giả định thế giới mở cho các loại lớp nghĩa là trình biên dịch phải tính đến khả năng của trường hợp. Mặc dù GHC chắc chắn thông minh hơn trình biên dịch trung bình, bạn càng có thể cung cấp nhiều thông tin hơn để giúp bạn.

Thứ hai, điều này có thể tạo ra nút cổ chai cho các cấu trúc dữ liệu phức tạp hơn. Bất kỳ hàm generic nào trên các kiểu được bọc sẽ bị ràng buộc với giao diện được trình bày bởi loại lớp, vì vậy trừ khi giao diện đó đầy đủ cả về tính biểu đạt và hiệu quả, bạn chạy nguy cơ của các thuật toán sử dụng loại hoặc thay đổi kiểu lớp nhiều lần khi bạn tìm thấy chức năng bị thiếu. Mặt khác, nếu kiểu bọc đã được giữ trừu tượng (tức là, nó không xuất các nhà xây dựng) thì vấn đề nút cổ chai là không liên quan, do đó, một loại lớp có thể có ý nghĩa tốt. Nếu không, tôi có thể đi với các thẻ kiểu ma (hoặc có thể là cách nhận dạng Functor mà sclv mô tả).

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