2011-12-17 18 views
14

Vì vậy, nói rằng tôi có một lớp:chức năng đa hình về hiện sinh loại

class C a where 
    reduce :: a -> Int 

Bây giờ tôi muốn để đóng gói nó trong một kiểu dữ liệu:

data Signal = forall a. (C a) => Signal [(Double, a)] 

Nhờ có sự định lượng hiện sinh, tôi có thể gọi Các phương thức C trên Tín hiệu, nhưng Tín hiệu không hiển thị thông số loại:

reduceSig :: Signal -> [(Double, Int)] 
reduceSig (Signal sig) = map (second reduce) sig 

Vì C có một số phương pháp tự nhiên của tôi bước tiếp theo là kéo chức năng 'giảm' để tôi có thể thay thế bất kỳ phương thức nào:

mapsig :: (C a) => (a -> a) -> Signal -> Signal 
mapsig f (Signal sig) = Signal (map (second f) sig) 

Lỗi loại! Không thể suy ra (a1 ~ a). Trên suy nghĩ thêm, tôi nghĩ rằng những gì nó nói là 'f' là một chức năng trên một số trường hợp của C, nhưng tôi không thể đảm bảo nó là cùng một ví dụ của C như trong các tín hiệu, vì các thông số loại được che dấu! Tôi muốn, tôi hiểu rồi.

Vậy điều này có nghĩa là không thể khái quát hóa reduceSig? Tôi có thể sống với điều này, nhưng tôi được sử dụng để tự do bao gồm các chức năng trong haskell nó cảm thấy lạ để có nghĩa vụ phải viết boilerplate. Mặt khác, tôi không thể nghĩ ra bất kỳ cách nào để thể hiện rằng một loại là bằng loại bên trong của tín hiệu, ngắn cho tín hiệu một tham số kiểu.

+0

Nhân tiện, trừ khi 'C' chứa nhiều hơn bạn có ở đây, loại' Tín hiệu' của bạn tương đương với 'dữ liệu Tín hiệu = Tín hiệu [(Đôi, Int) ] '! Vì vậy, không có lợi thế để sử dụng một loại tồn tại ở đây, trừ khi đây là một vấn đề đơn giản hóa (xem thêm: [1] (http://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/) , [2] (http://www.haskell.org/haskellwiki/FAQ#I.27m_making_an_RPG._Should_I_define_a_type_for_each_kind_of_monster.2C_and_a_type_class_for_them.3F), mặc dù các tình huống mà chúng giải quyết không hoàn toàn tương tự). – ehird

+0

Vâng, C có một loạt các phương pháp, tôi chỉ cố gắng đơn giản hóa cho ví dụ. Nhưng khi bạn chỉ ra, đơn giản hóa quá nhiều và lý do toàn bộ biến mất :) –

Trả lời

17

Những gì bạn cần phải thể hiện là f, như reduce sử dụng trong reduceSig, có thể được áp dụng cho bất kỳ kiểu đó là một thể hiện của C, như trái ngược với các loại hiện nay, nơi f công trình trên một loại duy nhất đó là một thể hiện của C. Điều này có thể được thực hiện như vậy:

mapsig :: (forall a. (C a) => a -> a) -> Signal -> Signal 
mapsig f (Signal sig) = Signal (map (second f) sig) 

Bạn sẽ cần tiện ích mở rộng RankNTypes, như bạn thường làm khi sử dụng các loại tồn tại; lưu ý rằng việc triển khai của mapsig giống nhau, loại vừa được khái quát hóa.

Về cơ bản, với loại này, mapsig sẽ quyết định xem một chức năng nào được gọi; với loại trước đó của bạn, người gọi của mapsig sẽ quyết định rằng, điều này không hiệu quả, bởi vì chỉ mapsig biết chính xác a, tức là bên trong số Signal.

Tuy nhiên, mapsig reduce không làm việc, với lý do rõ ràng rằng reduce :: (C a) => a -> Int, và bạn không biết rằng một là Int! Bạn cần phải cung cấp cho mapsig một loại tổng quát hơn (với việc thực hiện tương tự):

mapsig :: (C b) => (forall a. (C a) => a -> b) -> Signal -> Signal 

tức, f là một chức năng chụp bất kỳ loại đó là một thể hiện của C, và sản xuất một loại đó là một thể hiện của C (loại đó được cố định tại thời điểm cuộc gọi mapsig và được người gọi chọn;trong khi giá trị mapsig f có thể được gọi vào bất kỳ tín hiệu, nó sẽ luôn luôn tạo ra một tín hiệu với cùng một kết quả là (không phải là bạn có thể kiểm tra điều này từ bên ngoài).)

Existentials và xếp hạng-N loại rất thực sự khó khăn, vì vậy điều này có thể mất một chút thời gian để tiêu hóa. :)


Là một phụ lục, đó là giá trị chỉ ra rằng nếu tất cả các chức năng trong C nhìn như a -> r đối với một số r, sau đó bạn sẽ được tốt hơn tạo ra một kỷ lục thay vào đó, tức là quay

class C a where 
    reduce :: a -> Int 
    foo :: a -> (String, Double) 
    bar :: a -> ByteString -> Magic 

data Signal = forall a. (C a) => Signal [(Double, a)] 

mapsig :: (C b) => (forall a. (C a) => a -> b) -> Signal -> Signal 

vào

data C = C 
    { reduce :: Int 
    , foo :: (String, Double) 
    , bar :: ByteString -> Magic 
    } 

data Signal = Signal [(Double, C)] 

mapsig :: (C -> C) -> Signal -> Signal 

hai loại tín hiệu thực sự tương đương! Lợi ích của giải pháp cũ chỉ xuất hiện khi bạn có các loại dữ liệu khác sử dụng số Cmà không cần định lượng nó, để bạn có thể có mã sử dụng kiến ​​thức đặc biệt và hoạt động của cá thể cụ thể là C. Nếu các trường hợp sử dụng chính của lớp này là thông qua định lượng tồn tại, có thể bạn không muốn nó ở vị trí đầu tiên. Nhưng tôi không biết chương trình của bạn trông như thế nào :)

+1

Aha, tôi nghĩ rằng nó sẽ liên quan đến RankNTypes bằng cách nào đó, nhưng tôi không biết rằng nó sẽ chọn một loại cho 'a' để đồng ý với loại ẩn bên trong Signal. Đó là khá mát mẻ. Trên thực tế, nó chỉ ra tôi hơi misstated vấn đề của tôi, 'C a' có khác nhau 'a-> một' chức năng, và những người đang hạnh phúc với mapig đầu tiên. Bạn nói đúng, thứ hai là một chút huyền diệu và tôi sẽ phải suy nghĩ nhiều hơn về điều đó. Cảm ơn sự khai sáng! –

+0

Vâng, nó có thể chọn bất kỳ * a * nó muốn, và nó * có * để chọn một duy nhất * a * nó không biết gì về - không phải là một lựa chọn khó khăn cho một kiểm tra kiểu :) Tôi vui vì bạn thấy nó giác ngộ . – ehird

+0

(Lưu ý rằng bạn có thể rephrase 'a -> a' function bên trong' C' typeclass là 'C' fields của kiểu dữ liệu' C'. Tôi không biết thiết kế của bạn, tất nhiên, chỉ cần ném nó ra khỏi đó.) – ehird

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