2011-12-16 26 views
16

Đó là khá dễ dàng để xác định một nhà điều hành nhưNested ứng dụng chức năng

(@) :: [x -> y] -> [x] -> [y] 

mà phải mất một danh sách các hàm và một danh sách các đầu vào và trả về một danh sách các kết quả đầu ra. Có hai cách rõ ràng để thực hiện điều này:

  1. Áp dụng các chức năng đầu tiên vào đầu vào đầu tiên, chức năng thứ hai để các đầu vào thứ hai, vv
  2. Áp dụng tất cả các chức năng để mỗi đầu vào.

Hoặc là không đáng kể để xác định. Điều tốt đẹp về nó là bây giờ bạn có thể làm điều gì đó như

foo :: X -> Y -> Z -> R 

bar :: [X] -> [Y] -> [Z] -> [R] 
bar xs ys zs = [foo] @@ xs @@ ys @@ zs 

Điều này nói chung với một số đối số chức năng tùy ý.


Cho đến nay rất tốt. Bây giờ cho các vấn đề: Làm thế nào để tôi thay đổi chữ ký kiểu cho @@ như vậy mà các loại chữ ký cho bar trở thành

bar :: [X] -> [Y] -> [Z] -> [[[R]]] 

Nó không khó để thực hiện một chức năng với loại hình này; một trong hai cách này sẽ làm điều đó:

bar xs ys zs = map (\ x -> map (\ y -> map (\ z -> foo x y z) zs) ys) zs 
bar xs ys zs = map (\ z -> map (\ y -> map (\ x -> foo x y z) xs) ys) zs 

Tôi không kén chọn kết quả mình nhận được. Nhưng tôi không thể tìm ra cách tinh chỉnh toán tử @@ để thực hiện điều này.


Một điều hiển nhiên phải cố gắng là

(@@) :: [x -> y] -> [x] -> [[y]] 

Nó không khó để thực hiện điều này, nhưng nó sẽ không giúp bạn. Bây giờ bạn có

[foo] @@ xs :: [[Y -> Z -> R]] 

không phải là đầu vào hợp lệ cho @@. Không có cách rõ ràng để biết có bao nhiêu cấp độ của danh sách để đạt được thông qua để có được chức năng; rõ ràng cách tiếp cận này là một kết thúc chết.

Tôi đã thử một vài chữ ký có thể có khác, nhưng không có chữ ký nào đưa tôi đến gần câu trả lời hơn. Ai đó có thể cho tôi một giải pháp, hoặc giải thích tại sao không tồn tại?

+1

Sử dụng typeclass. – dave4420

+10

Không liên quan đến câu hỏi, tuy nhiên, bản gốc '(@@)' (phiên bản thứ 2) tương đương với cá thể 'Áp dụng' cho danh sách. Phiên bản đầu tiên, áp dụng từng hàm cho mỗi đầu vào, là cá thể 'Applicative' cho kiểu mới' ZipList'. –

+0

Sự khác biệt giữa 'mỗi' và 'mọi' là gì? – Prateek

Trả lời

7

Thực ra, điều này không cần các lớp loại nào cả! Bạn mất một chút tiện lợi bằng cách tránh các lớp học, nhưng đó là tất cả.

Điểm mấu chốt là, mặc dù sử dụng lại một bộ phối hợp đơn, tính đa hình cho phép loại sử dụng khác nhau. Đây là nguyên tắc tương tự đằng sau các biểu thức kiểu Applicative kiểu như f <$> xs <*> ys <*> zs và kết quả cuối cùng ở đây trông sẽ giống nhau. Do đó, chúng tôi sẽ làm điều đó cho bất kỳ Functor, không chỉ danh sách.

Sự khác biệt giữa điều này và phiên bản Applicative là chúng tôi đi sâu hơn vào lồng nhau Functor s với từng bước. Sự đa hình cần thiết đòi hỏi sự linh hoạt ở tầng bên trong nhất, vì vậy để thực hiện điều này, chúng ta sẽ sử dụng một thủ thuật tiếp tục-ish trong đó kết quả của mỗi bộ kết hợp là một hàm chấp nhận một phép biến đổi để sử dụng ở lớp trong cùng.

Chúng tôi sẽ cần hai toán tử, một toán tử khởi động chuỗi và một chuỗi tiếp tục tăng dần. Bắt đầu bằng cách sau:

(@@) :: (Functor f) => (((a -> b) -> f c) -> r) -> f a -> (b -> c) -> r 
q @@ xs = \k -> q (\f -> k . f <$> xs) 

Điều này cần một đối số mới ở bên phải và biểu thức đang diễn ra ở bên trái. Kết quả có một hàm k, xác định những gì cần làm để có được kết quả cuối cùng. k được kết hợp với bất kỳ biểu thức nào trong tiến trình đã có và cả hai được ánh xạ trên đối số mới. Điều này là phức tạp, nhưng nên nhìn quen thuộc với bất cứ ai đã lấy mã theo phong cách CPS.

(<@) :: (Functor f, Functor g) => f (a -> b) -> g a -> (b -> c) -> f (g c) 
fs <@ xs = (<$> fs) @@ xs 

Chuỗi được bắt đầu bằng cách ánh xạ mọi thứ khác lên đối số đầu tiên.

Không giống như trường hợp đơn giản Applicative, chúng tôi cũng cần kết thúc rõ ràng chuỗi. Giống như đơn vị Cont, cách đơn giản nhất để thực hiện điều này là áp dụng kết quả cho chức năng nhận dạng.Chúng tôi sẽ cung cấp cho rằng một tên hữu ích:

nested = ($ id) 

Bây giờ, chúng ta có thể làm những việc như thế này:

test2 :: [X -> Y -> R] -> [X] -> [Y] -> [[[R]]] 
test2 fs xs ys = nested (fs <@ xs @@ ys) 

test3 :: [X -> Y -> Z -> R] -> [X] -> [Y] -> [Z] -> [[[[R]]]] 
test3 fs xs ys zs = nested (fs <@ xs @@ ys @@ zs) 

Không khá xinh đẹp như các phiên bản kiểu lớp, nhưng nó hoạt động.

+0

Đó là khá ngọt ngào damned. Đây là loại câu trả lời tôi đã hy vọng! Cảm ơn bạn. – MathematicalOrchid

+0

@MathematicalOrchid: Trong trường hợp cụ thể này, cá nhân tôi có nhiều khả năng sử dụng các loại lớp - kết quả trông đẹp hơn khi được sử dụng và được cho là đơn giản hơn khi bạn biết cách hoạt động của nó. Nhưng các cách tiếp cận như tôi đã phác thảo vẫn đáng để suy nghĩ, vì khi các phương pháp khác không có sẵn vì một lý do nào đó. –

+1

@MathematicalOrchid: Oh, và kể từ khi bạn đề cập đến Oleg trong một bình luận về câu trả lời của John L - như bạn có thể nhận thức được, tiếp tục là một trong những đặc sản khác của mình bên cạnh loại hackery. Vì vậy, trong một cách, điều này thực sự có thể là cách tiếp cận phong cách Oleg bạn tự hỏi. –

8

Bạn đã đạt được lý do tại sao điều này là phiền hà. Chức năng của bạn (@@) được áp dụng cho các loại đầu vào khác nhau (ví dụ: [x->y], [[x -> y]], v.v. Điều này có nghĩa là chữ ký loại của bạn cho @@quá hạn chế, bạn sẽ cần thêm một số đa hình để làm cho nó trở nên phổ biến hơn. Vì nó xảy ra, với vấn đề này nếu bạn biết loại LHS, bạn có thể xác định duy nhất cả RHS và kết quả. đầu vào có loại [a->b], RHS phải là [a] và kết quả phải là [[b]]. Điều này có thể được đơn giản hóa thành đầu vào của a->b, RHS của [a], và kết quả của [b].Vì LHS xác định các tham số và kết quả khác, chúng ta có thể sử dụng hoặc fundeps hoặc loại gia đình để đại diện cho các loại khác.

{-# LANGUAGE TypeFamilies, UndecidableInstances #-} 

class Apply inp where 
    type Left inp :: * 
    type Result inp :: * 
    (@@) :: inp -> [Left inp] -> [Result inp] 

Bây giờ chúng ta có một lớp loại, chúng ta có thể làm cho các ví dụ rõ ràng cho một chức năng:

instance Apply (a -> b) where 
    type Left (a -> b) = a 
    type Result (a -> b) = b 
    (@@) = map 

Thể hiện danh sách không phải là quá xấu, hoặc:

instance Apply f => Apply [f] where 
    type Left [f] = Left f 
    type Result [f] = [Result f] 
    l @@ r = map (\f -> f @@ r) l 
    -- or map (@@ r) l 

Bây giờ phương thức lớp học của chúng tôi @@ sẽ hoạt động với danh sách sâu tùy ý. Dưới đây là một số thử nghiệm:

*Main> (+) @@ [1..3] @@ [10..13] 
[[11,12,13,14],[12,13,14,15],[13,14,15,16]]'s 

let foo :: Int -> Char -> Char -> String 
    foo a b c = b:c:show a 

*Main> foo @@ [1,2] @@ "ab" @@ "de" 
[[["ad1","ae1"],["bd1","be1"]],[["ad2","ae2"],["bd2","be2"]]] 

Bạn có thể muốn xem triển khai printf để có thêm cảm hứng.

Chỉnh sửa: Ngay sau khi đăng bài này, tôi nhận ra rằng người ta có thể khái quát hóa loại container trong lớp Apply của mình từ List đến Applicative, sau đó sử dụng thể hiện ứng dụng thay vì bản đồ. Điều này sẽ cho phép cả hai danh sách thông thường và hành vi ZipList.

+0

Đây thực sự là một câu trả lời cho câu hỏi này (http://stackoverflow.com/questions/8478067/ true-tables-from-anonymous-functions-in-haskell) và khi OP chỉ ra 'printf'. –

+0

Các thử nghiệm thú vị khác: '[negate, id] @@ [1..3]' và '[(+), (-)] @@ [1..3] @@ [11..13]' –

+1

Đó là một câu trả lời kỹ lưỡng và tất cả ... nhưng tôi đã hy vọng có một số cách để tránh cần một chiếc máy đánh chữ. Các lớp học cuối cùng được desugared trong quá trình biên dịch, do đó, phải có _some_ cách làm việc đó. Tôi cho rằng đó chỉ là một câu hỏi về sự bất tiện của nó như thế nào ... Chắc chắn có vẻ như nó có thể khiến Oleg giải quyết nó theo cách đó. o_O – MathematicalOrchid

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