2015-01-17 12 views
12

Tôi không phải là fan hâm mộ lớn nhất của varargs, nhưng tôi luôn nghĩ rằng cả hai ứng dụng (f <$> x <*> y) và thành ngữ ([i| f x y |]) phong cách có quá nhiều biểu tượng. Tôi thường thích đi theo cách liftA2 f x y, nhưng tôi cũng nghĩ rằng A2 hơi xấu xí. Từ this question, tôi đã học được rằng có thể triển khai các hàm vararg trong Haskell. Bằng cách này, là nó có thể sử dụng cùng một nguyên tắc để thực hiện một chức năng nâng, chẳng hạn rằng:Có thể mã hóa chức năng "nâng" chung trong Haskell không?

lift f a b == pure f <*> a <*> b 

Tôi đã thử thay thế + bởi <*> trên mã niêm yết:

class Lift r where 
    lift :: a -> r 

instance Lift a where 
    lift = id 

instance (Lift r) => Lift (a -> r) where 
    lift x y = lift (x <*> y) 

Nhưng Tôi không thể quản lý đúng loại ...

+3

Loại bạn đang cố gắng nâng _to_? Tôi thấy '<*>' trong mã, nhưng tôi không thấy bất kỳ đề cập nào về 'Áp dụng' trong các chữ ký kiểu ... Dù bằng cách nào, tôi nghi ngờ trường hợp' Nâng a' sẽ là một vấn đề, vì nó trùng lặp với mọi thể hiện có thể khác (bao gồm 'Nâng (a -> r)'). – MathematicalOrchid

+0

Nevermind mã của tôi, tôi đã thử một tấn những thứ (có lẽ là vô nghĩa), chỉ cần đăng một số ảnh chụp ngẫu nhiên vì lợi ích của nó. Tôi thực sự bối rối với khái niệm này, bởi vì, ví dụ, 'lift (pure (+)) (Just 1) (Chỉ 2)' - ở đây, '(pure (+))' có một kiểu khác với 'Just 1 ', nhưng cấu trúc được cung cấp là hardcoded cho một kiểu' Integer' ... Tôi cũng cần một cách để mã hóa một cá thể cho "bất kỳ kiểu nào không phải là một hàm", như là một điều kiện chấm dứt cho việc loại bỏ kiểu. – MaiaVictor

Trả lời

19

Lưu ý rằng bạn có thể chuỗi bất kỳ số nào là <*>, để có được chức năng của biểu mẫu

f (a0 -> .. -> an) -> (f a0 -> .. -> f an) 

Nếu chúng tôi có loại a0 -> .. -> anf a0 -> .. -> f an, chúng tôi có thể tính f từ điều này. Chúng tôi có thể mã hóa mối quan hệ này, và loại chung nhất, như sau

class Lift a f b | a b -> f where 
    lift' :: f a -> b 

Như bạn có thể mong đợi, các "trường hợp đệ quy" dụ sẽ chỉ cần áp dụng <*> một lần, sau đó recurse:

instance (a ~ a', f' ~ f, Lift as f rs, Applicative f) 
     => Lift (a -> as) f (f' a' -> rs) where 
    lift' f a = lift' $ f <*> a 

Các cơ sở trường hợp là khi không có thêm chức năng. Vì bạn không thể thực sự khẳng định "a không phải là một loại chức năng", điều này phụ thuộc vào trường hợp chồng chéo:

instance (f a ~ b) => Lift a f b where 
    lift' = id 

Do GHCs quy tắc lựa chọn Ví dụ, trường hợp đệ quy sẽ luôn luôn được lựa chọn, nếu có thể.

Sau đó các chức năng bạn muốn là lift' . pure:

lift :: (Lift a f b, Applicative f) => a -> b 
lift x = lift' (pure x) 

Đây là nơi mà phụ thuộc chức năng trên Lift trở nên rất quan trọng. Vì f chỉ được đề cập trong ngữ cảnh, chức năng này sẽ bị đánh máy sai trừ khi chúng tôi có thể xác định f chỉ biết ab (xuất hiện ở bên phải của =>).

Điều này đòi hỏi nhiều phần mở rộng:

{-# LANGUAGE 
    OverlappingInstances 
    , MultiParamTypeClasses 
    , UndecidableInstances 
    , FunctionalDependencies 
    , ScopedTypeVariables 
    , TypeFamilies 
    , FlexibleInstances 
    #-} 

và, như thường lệ với các chức năng variadic trong Haskell, thường là cách duy nhất để chọn một thể hiện là để cho một chữ ký kiểu tường minh.

lift (\x y z -> x * y + z) readLn readLn readLn :: IO Int 

Con đường tôi đã viết nó, GHC sẽ vui vẻ chấp nhận lift đó là đa hình trong lập luận để f (nhưng không f chính nó).

lift (+) [1..5] [3..5] :: (Enum a, Num a) => [a] 

Đôi khi bối cảnh đủ để suy ra loại chính xác. Lưu ý rằng loại đối số là một lần nữa đa hình.

main = lift (\x y z -> x * y + z) readLn readLn readLn >>= print 

Tính đến GHC> = 7.10, OverlappingInstances đã bị phản đối và trình biên dịch sẽ phát hành một cảnh báo. Nó có thể sẽ được gỡ bỏ trong một số phiên bản sau này. Điều này có thể được sửa chữa bằng cách loại bỏ OverlappingInstances từ {-# LANGUAGE .. #-} pragma và thay đổi Ví dụ 2 đến

instance {-# OVERLAPS #-} (f a ~ b) => Lift a f b where 
+0

Ôi trời, thật tuyệt vời. Không có thắc mắc tôi đã bị mất, tôi không có một nửa kiến ​​thức cần thiết để thực hiện điều đó. Có một cuốn sách nào có giải thích sâu sắc về những phức tạp của hệ thống kiểu này hay bạn đã học nó bằng kinh nghiệm? Cảm ơn bạn!Chỉnh sửa: Tôi đoán bạn cần "TypeFamilies" và "FlexibleInstances" quá ... – MaiaVictor

+1

Tôi luôn luôn quên những phần mở rộng tồn tại bởi vì tôi có chúng luôn luôn bật. Tôi sẽ bao gồm điều đó. Chi tiết về các phần mở rộng hệ thống kiểu GHC được tìm thấy chủ yếu trong [hướng dẫn sử dụng] (https://downloads.haskell.org/~ghc/7.8.3/docs/html/users_guide/). Nó không phải là rất sâu, tuy nhiên, và đôi khi khó hiểu, nhưng hữu ích cho sự hiểu biết những gì mỗi phần mở rộng không, không quá nhiều làm thế nào để sử dụng nó. Hầu hết các loại hack-trickery và thủ thuật tôi biết tôi đã học được kinh nghiệm, và [Oleg] (http://okmij.org/ftp/). – user2407038

+1

Tại sao tiện ích 'TypeFamilies'? Tôi không nhìn thấy nơi bạn đang sử dụng chúng ... nó có thể có thể sử dụng gia đình loại * thay vì * phụ thuộc chức năng, nhưng tôi không thấy lý do tại sao cả hai sẽ được yêu cầu ... A, chờ đợi. bạn cần điều đó cho các ràng buộc kiểu? – Bakuriu

7

Tôi giả sử bạn muốn sử dụng lift không có loại chú thích. Trong trường hợp này về cơ bản có hai lựa chọn:

Đầu tiên, nếu chúng ta sử dụng OverlappingInstances, chức năng đa hình cần chú thích:

{-# LANGUAGE 
    OverlappingInstances, 
    MultiParamTypeClasses, 
    UndecidableInstances, 
    FunctionalDependencies, 
    FlexibleInstances, 
    TypeFamilies 
    #-} 

import Control.Applicative 

class Applicative f => ApN f a b | a b -> f where 
    apN :: f a -> b 

instance (Applicative f, b ~ f a) => ApN f a b where 
    apN = id 

instance (Applicative f, ApN f a' b', b ~ (f a -> b')) => ApN f (a -> a') b where 
    apN f fa = apN (f <*> fa) 

lift :: ApN f a b => a -> b 
lift a = apN (pure a) 

-- Now we can't write "lift (+) (Just 0) Nothing" 
-- We must annotate as follows: 
-- lift ((+) :: Int -> Int -> Int) (Just 0) Nothing 
-- Monomorphic functions work fine though: 
-- lift (||) (Just True) (Just True) --> results in "Just True" 

Second, nếu chúng ta thay vì sử dụng IncoherentInstances, lift sẽ làm việc mà không chú thích thậm chí về chức năng đa hình. Tuy nhiên, một số nội dung phức tạp vẫn không kiểm tra, ví dụ: (lift . lift) (+) (Just (Just 0)) Nothing.

{-# LANGUAGE 
    IncoherentInstances, MultiParamTypeClasses, 
    UndecidableInstances,ScopedTypeVariables, 
    AllowAmbiguousTypes, FlexibleInstances, TypeFamilies 
    #-} 

import Control.Applicative 

class Applicative f => ApN f a b where 
    apN :: f a -> b 

instance (Applicative f, b ~ f a) => ApN f a b where 
    apN = id 

instance (Applicative f, ApN f a' b', b ~ (f a -> b')) => ApN f (a -> a') b where 
    apN f fa = apN (f <*> fa) 

lift :: forall f a b. ApN f a b => a -> b 
lift a = (apN :: f a -> b) (pure a) 

-- now "lift (+) (Just 0) (Just 10)" works out of the box 

tôi đã trình bày hai giải pháp thay vì chỉ một với IncoherentInstancesIncoherentInstances là một phần mở rộng khá thô mà nên tránh nếu có thể. Nó có thể là tốt ở đây, nhưng tôi nghĩ nó đáng giá để cung cấp một giải pháp thay thế, anyway.


Trong cả hai trường hợp, tôi sử dụng cùng một mẹo để trợ giúp suy luận và giảm chú thích: Tôi cố gắng di chuyển thông tin từ các cá thể dụ sang ràng buộc đối tượng. Vì vậy, thay vì

instance (Applicative f) => ApN f a (f a) where 
    apN = id 

tôi viết

instance (Applicative f, b ~ f a) => ApN f a b where 
    apN = id 

Ngoài ra, trong trường hợp khác mà tôi sử dụng một tham số đơn giản b vào đầu dụ và thêm b ~ (f a ~ b') sự ràng buộc.

Lý do để thực hiện điều này là trước tiên GHC sẽ kiểm tra xem có đầu kết hợp phù hợp không và chỉ cố gắng giải quyết các ràng buộc sau khi có kết quả khớp thành công. Chúng tôi muốn đặt ít nhất gánh nặng lên đầu thể hiện và để người giải quyết hạn chế sắp xếp mọi thứ (vì nó linh hoạt hơn, có thể trì hoãn việc đánh giá và có thể sử dụng các ràng buộc từ các phần khác của chương trình).

+0

Đó là một câu trả lời tuyệt vời, có lẽ thậm chí còn đầy đủ hơn câu trả lời đầu tiên. Tôi đã không mong đợi hai câu trả lời cho một cái gì đó tôi xem xét khá phức tạp. Cảm ơn bạn! – MaiaVictor

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