2011-10-22 16 views
5

Để giữ cho nó đơn giản, tôi sẽ sử dụng lớp ví dụ contrived này (Vấn đề là chúng ta có một số dữ liệu đắt tiền có nguồn gốc từ phương pháp):Memoization và typeclasses

class HasNumber a where 
    getNumber :: a -> Integer 
    getFactors :: a -> [Integer] 
    getFactors a = factor . getNumber 

Tất nhiên, chúng ta có thể làm cho việc triển khai memoizing của lớp này như:

data Foo = Foo { 
    fooName :: String, 
    fooNumber :: Integer, 
    fooFactors :: [Integer] 
} 

foo :: String -> Integer -> Foo 
foo a n = Foo a n (factor n) 

instance HasNumber Foo where 
    getNumber = fooNumber 
    getFactors = fooFactors 

Nhưng nó có vẻ hơi xấu xí để được yêu cầu tự thêm một lĩnh vực 'yếu tố' đến bất cứ hồ sơ đó sẽ là một ví dụ HasNumber. Ý tưởng tiếp theo:

data WithFactorMemo a = WithFactorMemo { 
    unWfm :: a, 
    wfmFactors :: [Integer] 
} 

withFactorMemo :: HasNumber a => a -> WithFactorMemo a 
withFactorMemo a = WithFactorMemo a (getFactors a) 

instance HasNumber a => HasNumber (WithFactorMemo a) where 
    getNumber = getNumber . unWfm 
    getFactors = wfmFactors 

này sẽ đòi hỏi rất nhiều soạn sẵn để nâng tất cả các hoạt động khác của bản gốc a vào WithFactorMemo a, mặc dù.

Có giải pháp thanh lịch nào không?

+0

Một giải pháp khác mà tôi cho là sẽ làm cho chức năng * yếu tố * ghi nhớ, mặc dù điều này sẽ ít thực tế hơn nếu kết quả của 'getNumber' là cấu trúc dữ liệu lớn hơn, và (AFAIK) (trái ngược với hai giải pháp trong câu hỏi của tôi). – FunctorSalad

Trả lời

7

Đây là giải pháp: mất kiểu chữ. Tôi đã nói về điều này herehere. Bất kỳ typeclass TC a mà mỗi thành viên của nó lấy một a là một đối số là đẳng cấu cho một kiểu dữ liệu. Điều đó có nghĩa rằng tất cả các thể hiện của lớp HasNumber của bạn có thể được đại diện trong kiểu dữ liệu này:

data Number = Number { 
    getNumber' :: Integer, 
    getFactors' :: [Integer] 
} 

Cụ thể, bằng cách chuyển đổi này:

toNumber :: (HasNumber a) => a -> Number 
toNumber x = Number (getNumber x) (getFactors x) 

Number rõ ràng là một thể hiện của HasNumber là tốt.

instance HasNumber Number where 
    getNumber = getNumber' 
    getFactors = getFactors' 

Tính đẳng hình này cho chúng ta thấy lớp này là loại dữ liệu ngụy trang và phải chết. Chỉ cần sử dụng Number để thay thế. Nó có thể ban đầu không rõ ràng làm thế nào để làm điều này, nhưng với một chút kinh nghiệm nên đến một cách nhanh chóng. . Ví dụ, gõ Foo của bạn trở thành:

data Foo = Foo { 
    fooName :: String, 
    fooNumber :: Number 
} 

memoization của bạn sau đó sẽ đến miễn phí, bởi vì các yếu tố này được lưu trữ trong cơ cấu Number dữ liệu.

+0

Trên thực tế, đây là những gì tôi quyết định thử quá ngay trước khi bạn đăng :) Tôi đồng ý với việc đưa các hoạt động vào một loại duy nhất ('Số' ở đây), nhưng có lẽ nó vẫn là một ý tưởng hay để có một' lớp HasNumber một nơi numberDict :: a -> Số' cùng với các trình bao bọc 'getNumber = getNumber '. numberDict' và vân vân. Nhưng người ta phải thực sự lưu trữ 'Số' trong các bản ghi phải là' HasNumber', thay vì tạo một 'Số' từ một Số nguyên trong thực thi' numberDict' (dĩ nhiên, sẽ để lại cho chúng ta mà không cần ghi nhớ lại) . – FunctorSalad

+0

Tôi rất khuyên bạn nên chống lại typeclass trong trường hợp như thế này, nó sẽ chỉ nhận được theo cách của bạn. Chỉ cần mô hình hóa nó một cách cụ thể, hộp công cụ FP phù hợp hơn với loại lập trình đó, ngôn ngữ tốt hơn trong việc trừu tượng hóa các kiểu dữ liệu hơn so với kiểu chữ, và nó không cho phép bạn lừa dối chính mình rằng bạn đang làm mô hình OO (mà bạn không - và nếu bạn đang suy nghĩ theo cách đó, ngay cả khi không nhận ra nó, ngôn ngữ sẽ kết thúc hạn chế bạn xuống đường). – luqui

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