2011-06-26 19 views
11

Có thể mô phỏng một hàm với kiểu dữ liệu của riêng bạn với một số phần mở rộng GHC không? Điều tôi muốn làm là ví dụ:Có thể mô phỏng một hàm bằng cách sử dụng kiểu dữ liệu của riêng bạn không?

(cú pháp tưởng tượng)

data MyFunc = MyFunc String (Int->Int) 

instance (Int->Int) MyFunc where 
    ($) (MyFunc _ f) i = f i 

inc = MyFunc "increment" (1+) 

test = inc 1

Tức là dữ liệu mang một số thông tin meta với nó và có thể là mẫu phù hợp, nhưng vẫn có thể được gọi là một hàm bình thường. Bây giờ, tôi biết rằng tôi có thể định nghĩa toán tử của riêng mình như $$ và gọi inc $$ 1, nhưng việc có thể sử dụng cú pháp gọi hàm thường xuyên sẽ rất hữu ích trong các DSL nhúng.

Trả lời

18

Có, nó có thể được thực hiện ở một mức độ giới hạn.

Nhưng trước tiên chúng ta cần

{-# LANGUAGE Rank2Types #-} 

Hãy xác định

data M a b = M { name :: Int -> String -> String, eval :: a -> b } 

tôi thêm cấu trúc cho tên của bạn để tôi có thể nhận được hỗ trợ chương trình đẹp hơn. ;)

Sau đó, cho phép xác định một lớp:

class Magic m where 
    magic :: M a b -> m a b 

instance Magic M where 
    magic = id 

instance Magic (->) where 
    magic (M _ f) = f 

Bây giờ, hãy xem xét loại:

type MyFunc a b = forall m. Magic m => m a b 

Kiểu kết quả của magic là một trong hai (a -> b) hoặc một M a b.

Vì vậy, nó có thể được sử dụng như một thành viên của MyFunc. Bây giờ, loại này hơi không thỏa mãn, bởi vì bạn không thể tạo các trường hợp gửi đi, nhưng điều đó có nghĩa là

inc :: MyFunc Int Int 
inc = magic (M (const (showString "inc")) (+1)) 

test :: Int 
test = inc 1 

chỉ hoạt động tốt.

Chúng tôi thậm chí có thể tạo ra một cách khá hay để hiển thị chúng. Mặc dù chúng tôi không thể sử dụng hiển thị trên MyFunc, chúng tôi có thể xác định nó cho M.

instance Show (M a b) where 
    showsPrec d (M s _) = s d 

Sau đó, chúng ta có thể thực hiện một chức năng chúng ta có thể áp dụng đối với M a b (và bằng cách mở rộng bất kỳ MyFunc) để lấy ra một M a b.

m :: M a b -> M a b 
m = id 

và chúng ta có thể định nghĩa một combinator đặc biệt để hiển thị MyFunc s:

showM :: MyFunc a b -> String 
showM f = show (m f) 

Sau đó chúng ta có thể chơi. Chúng tôi có thể xác định các chế phẩm của MyFunc s.

infixr 9 .# 
(.#) :: MyFunc b c -> MyFunc a b -> MyFunc a c 
f .# g = magic (M 
    (\d -> showParen (d > 9) $ showsPrec 10 (m f) . 
           showString " . " . 
           showsPrec 9 (m g)) 
    (f . g)) 

inc2 :: MyFunc Int Int 
inc2 = inc .# inc 

test2 :: Int 
test2 = inc2 1 

bar, baz :: String 
bar = showM inc 
baz = showM inc2 

Và bởi vì tôi đã cung cấp đủ cấu trúc cho tên, chúng tôi thậm chí có được dấu ngoặc đơn chính xác cho các tác phẩm phức tạp hơn mà không cần dấu ngoặc đơn.

*Main> showM $ inc2 .# inc 
"(inc . inc) . inc" 

*Main> showM $ inc .# inc2 
"inc . inc . inc" 

Nhưng hãy nhớ, bạn sẽ không thể xác định bất kỳ trường hợp cho MyFunc, vì nó chỉ có thể là một type, và không phải là một newtype. Để xác định các cá thể, bạn sẽ phải xác định chúng trên M và sau đó sử dụng m để chuyển đổi thành loại đó để công văn tiềm ẩn có loại để lấy.

Do loại xếp hạng 2, nếu bạn sử dụng rất nhiều trong ngữ cảnh cục bộ, bạn cũng có thể muốn bật NoMonoLocalBinds và/hoặc NoMonomorphismRestriction.

+9

Đây là loại đáng sợ. Tôi thích nó. –

5

Không, cú pháp f e không thể bị quá tải. f phải có loại S -> T.

Nhưng bạn vẫn có thể làm được nhiều việc với EDSL nếu bạn nhúng sâu, tức là, bạn cho phép các chức năng của bạn xây dựng cây cú pháp thay vì tính toán.

3

Bạn không thể trực tiếp quá tải cú pháp cuộc gọi hàm, không.

Bạn có thể làm cho loại mũi tên tùy chỉnh của riêng bạn --- xem Control.Arrow. Sau đó, bạn có thể gọi "chức năng" của mình bằng cách sử dụng arrow notation (bạn sẽ cần phải bao gồm {-# LANGUAGE Arrows #-} ở đầu tệp nguồn của bạn). Cho dù điều này là đủ tốt cho bạn phụ thuộc vào nhu cầu của DSL của bạn.

+0

++ 1 cho các mũi tên. – fuz

+0

Đừng quên ứng viên! Họ cũng có một nhà điều hành ứng dụng. – luqui

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