2009-12-13 32 views
11

Giả sử rằng tôi có hai kiểu dữ liệu Foo và Bar. Foo có các trường x và y. Thanh có các trường x và z. Tôi muốn có thể viết một hàm nhận Foo hoặc Bar làm tham số, trích xuất giá trị x, thực hiện một số phép tính trên nó, và sau đó trả về một Foo hoặc Bar mới với giá trị x được thiết lập tương ứng.Cú pháp ghi Haskell và loại lớp

Dưới đây là một cách tiếp cận:

class HasX a where 
    getX :: a -> Int 
    setX :: a -> Int -> a 

data Foo = Foo Int Int deriving Show 

instance HasX Foo where 
    getX (Foo x _) = x 
    setX (Foo _ y) val = Foo val y 

getY (Foo _ z) = z 
setY (Foo x _) val = Foo x val 

data Bar = Bar Int Int deriving Show 

instance HasX Bar where 
    getX (Bar x _) = x 
    setX (Bar _ z) val = Bar val z 

getZ (Bar _ z) = z 
setZ (Bar x _) val = Bar x val 

modifyX :: (HasX a) => a -> a 
modifyX hasX = setX hasX $ getX hasX + 5 

Vấn đề là tất cả những getter và setter là đau đớn để viết, đặc biệt là nếu tôi thay Foo và Bar với các kiểu dữ liệu thực tế rằng có rất nhiều lĩnh vực.

Cú pháp ghi của Haskell mang đến một cách xác định tốt hơn các bản ghi này. Nhưng, nếu tôi cố gắng xác định các bản ghi như thế này

data Foo = Foo {x :: Int, y :: Int} deriving Show 
data Bar = Foo {x :: Int, z :: Int} deriving Show 

Tôi sẽ gặp lỗi khi nói rằng x được xác định nhiều lần. Và, tôi không thấy bất kỳ cách nào để làm cho một phần của một lớp loại để tôi có thể vượt qua chúng để sửa đổiX.

Có cách nào tốt đẹp để giải quyết vấn đề này không, hoặc tôi có bị mắc kẹt với việc xác định getters và setters của riêng mình không? Đặt một cách khác, là có một cách để kết nối các chức năng được tạo ra bởi cú pháp kỷ lục với các loại lớp (cả getters và setters)?

EDIT

Đây là vấn đề thực sự tôi đang cố giải quyết. Tôi đang viết một loạt các chương trình liên quan mà tất cả đều sử dụng System.Console.GetOpt để phân tích các tùy chọn dòng lệnh của họ. Sẽ có rất nhiều tùy chọn dòng lệnh phổ biến trên các chương trình này, nhưng một số chương trình có thể có các tùy chọn bổ sung. Tôi muốn mỗi chương trình có thể xác định một bản ghi chứa tất cả các giá trị tùy chọn của nó. Sau đó tôi bắt đầu với một giá trị bản ghi mặc định sau đó được chuyển đổi thông qua một đơn vị StateT và GetOpt để có được một bản ghi cuối cùng phản ánh các đối số dòng lệnh. Đối với một chương trình duy nhất, cách tiếp cận này hoạt động thực sự tốt, nhưng tôi đang cố gắng tìm cách sử dụng lại mã trên tất cả các chương trình.

+3

Nếu bạn có một kiểu dữ liệu duy nhất 'dữ liệu FooBar = Foo {x :: Int, y :: Int} | Bar {x :: Int, z :: Int} 'bạn sẽ không gặp vấn đề này. Nếu các kiểu dữ liệu của bạn nằm trong các mô-đun khác nhau, bạn có thể sử dụng '{- # LANGUAGE DisambiguateRecordFields # -}'. Bất kỳ lý do gì để gắn bó với thiết kế hiện tại của bạn? – ephemient

Trả lời

5

Bạn muốn extensible records mà, tôi thu thập, là một trong những chủ đề được nói nhiều nhất trong Haskell. Dường như hiện tại không có nhiều sự đồng thuận về cách thực hiện nó.

Trong trường hợp của bạn, có vẻ như có thể thay vì bản ghi thông thường, bạn có thể sử dụng danh sách không đồng nhất như danh sách được triển khai trong HList.

Sau đó, một lần nữa, có vẻ như bạn chỉ có hai cấp ở đây: phổ biến và chương trình. Vì vậy, có lẽ bạn chỉ nên xác định một loại bản ghi phổ biến cho các tùy chọn phổ biến và một loại bản ghi chương trình cụ thể cho từng chương trình, và sử dụng StateT trên một bộ các loại đó. Đối với các công cụ phổ biến, bạn có thể thêm bí danh tạo fst với các trình truy cập thông thường để ẩn với người gọi.

+0

Ý tưởng thú vị về việc có loại bản ghi phổ biến và loại chương trình cụ thể. Tôi có thể có nhiều nhóm tính phổ biến hơn là chỉ phổ biến và chương trình (không chắc chắn), nhưng tôi có thể dễ dàng mở rộng cách tiếp cận của bạn để hỗ trợ điều này. (Ví dụ, tôi có thể chuyển qua danh sách các bản ghi liên kết thông qua StateT, và mỗi tùy chọn sẽ biết cách tìm kiếm bản ghi tương ứng của nó trong danh sách theo tên.) –

3

Bạn có thể sử dụng mã như

data Foo = Foo { fooX :: Int, fooY :: Int } deriving (Show) 
data Bar = Bar { barX :: Int, barZ :: Int } deriving (Show) 

instance HasX Foo where 
    getX = fooX 
    setX r x' = r { fooX = x' } 

instance HasX Bar where 
    getX = barX 
    setX r x' = r { barX = x' } 

bạn là người mẫu gì trong mã của bạn? Nếu chúng ta biết nhiều hơn về vấn đề này, chúng ta có thể đề nghị một điều gì đó ít khó xử hơn thiết kế hướng đối tượng này được tạo thành một ngôn ngữ chức năng.

+0

Cảm ơn bạn đã phản hồi. Cách tiếp cận của bạn đơn giản hoá mọi thứ một chút. Tôi đã sửa đổi câu hỏi ban đầu của mình để giải thích thêm về vấn đề thực sự mà tôi đang cố giải quyết. Tôi đồng ý rằng việc sử dụng cách tiếp cận OO với một ngôn ngữ chức năng thường không phải là cách tốt nhất. Tôi mới sử dụng các ngôn ngữ chức năng và vẫn cố gắng thoát khỏi tư duy OO của mình. :-) –

1

Nếu bạn thực hiện các trường hợp kiểu có thể gập lại, bạn sẽ có được hàm toList mà bạn có thể sử dụng làm cơ sở cho người truy cập của mình.

Nếu có thể gập lại không phải bởi bạn bất cứ điều gì, thì có thể cách tiếp cận đúng là xác định giao diện bạn muốn làm lớp loại và tìm ra cách tốt để tự động tạo các giá trị dẫn xuất.

Có lẽ bằng cách bắt nguồn từ làm

deriving(Data) 

bạn có thể sử dụng combinators GMap căn truy cập của bạn tắt.

2

Dường như với tôi như một công việc về thuốc generic. Nếu bạn có thể tag Int của bạn với newtypes khác nhau, sau đó bạn sẽ có thể viết (với uniplate, mô-đun PlateData):

data Foo = Foo Something Another deriving (Data,Typeable) 
data Bar = Bar Another Thing deriving (Data, Typerable) 

data Opts = F Foo | B Bar 

newtype Something = S Int 
newtype Another = A Int 
newtype Thing = T Int 

getAnothers opts = [ x | A x <- universeBi opts ] 

này sẽ trích xuất tất cả Một từ bất cứ nơi nào bên trong opts.

Có thể sửa đổi.

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