Khi nào thì -X ALLowAmbiguptTypes thích hợp?


212

Gần đây tôi đã đăng một câu hỏi về cú pháp-2.0 liên quan đến định nghĩa của share. Tôi đã làm việc này trong GHC 7.6 :

{-# LANGUAGE GADTs, TypeOperators, FlexibleContexts #-}

import Data.Syntactic
import Data.Syntactic.Sugar.BindingT

data Let a where
    Let :: Let (a :-> (a -> b) :-> Full b)

share :: (Let :<: sup,
          sup ~ Domain b, sup ~ Domain a,
          Syntactic a, Syntactic b,
          Syntactic (a -> b),
          SyntacticN (a -> (a -> b) -> b) 
                     fi)
           => a -> (a -> b) -> b
share = sugarSym Let

Tuy nhiên, GHC 7.8 muốn -XAllowAmbiguousTypesbiên dịch với chữ ký đó. Ngoài ra, tôi có thể thay thế fibằng

(ASTF sup (Internal a) -> AST sup ((Internal a) :-> Full (Internal b)) -> ASTF sup (Internal b))

đó là loại ngụ ý của các quỹ trên SyntacticN. Điều này cho phép tôi tránh phần mở rộng. Tất nhiên đây là

  • một loại rất dài để thêm vào một chữ ký đã lớn
  • mệt mỏi để lấy bằng tay
  • không cần thiết do quỹ

Câu hỏi của tôi là:

  1. Đây có phải là một sử dụng chấp nhận được -XAllowAmbiguousTypes?
  2. Nói chung, khi nào nên sử dụng phần mở rộng này? Một câu trả lời ở đây cho thấy "nó gần như không bao giờ là một ý tưởng hay".
  3. Mặc dù tôi đã đọc các tài liệu , tôi vẫn gặp khó khăn khi quyết định xem một ràng buộc có mơ hồ hay không. Cụ thể, hãy xem xét chức năng này từ Data.Syntactic.Sugar:

    sugarSym :: (sub :<: AST sup, ApplySym sig fi sup, SyntacticN f fi) 
             => sub sig -> f
    sugarSym = sugarN . appSym
    

    Tôi thấy rằng fi(và có thể sup) nên mơ hồ ở đây, nhưng nó biên dịch mà không có phần mở rộng. Tại sao sugarSymkhông rõ ràng trong khi sharelà? Vì sharelà một ứng dụng của sugarSym, sharetất cả các ràng buộc đến từ sugarSym.


4
Có bất kỳ lý do tại sao bạn không thể chỉ sử dụng loại suy ra cho sugarSym Let, đó là (SyntacticN f (ASTF sup a -> ASTF sup (a -> b) -> ASTF sup b), Let :<: sup) => fvà không liên quan đến các biến loại mơ hồ?
kosmikus

3
@kosmikus Sầu muộn phải mất rất lâu để trả lời. này không biên dịch với chữ ký được suy ra cho share, nhưng không biên dịch khi một trong hai chữ ký được đề cập trong câu hỏi được sử dụng. Câu hỏi của bạn cũng đã được hỏi trong các bình luận của một bài viết trước
crockeea

3
Hành vi không xác định có lẽ không phải là thuật ngữ thích hợp nhất. Thật khó hiểu khi chỉ dựa vào một chương trình. Vấn đề là sự quyết định và GHCI không thể chứng minh các loại trong chương trình của bạn. Có một cuộc thảo luận dài có thể khiến bạn quan tâm chỉ về chủ đề này. haskell.org/pipermail/haskell-cafe/2008-April/041397.html
BlamKiwi

6
Đối với (3), loại đó không mơ hồ vì Phụ thuộc chức năng trong định nghĩa của SyntacticN (nghĩa là f - »fi) và ApplySym (cụ thể là fi -> sig, sup). Từ đó, bạn nhận được rằng fmình là đủ để hoàn toàn disambiguate sig, fisup.
user2141650

3
@ user2141650 Xin lỗi, mất quá nhiều thời gian để trả lời. Bạn đang nói những fundep trên SyntacticNlàm cho firõ ràng trong sugarSym, nhưng sau đó tại sao là như nhau không đúng đối với fitrong share?
crockeea

Câu trả lời:


12

Tôi không thấy bất kỳ phiên bản cú pháp được xuất bản nào có chữ ký sugarSymsử dụng các tên loại chính xác đó, vì vậy tôi sẽ sử dụng nhánh phát triển tại commit 8cfd02 ^ , phiên bản cuối cùng vẫn sử dụng các tên đó.

Vì vậy, tại sao GHC phàn nàn về fichữ ký trong loại của bạn mà không phải là chữ ký cho sugarSym? Tài liệu bạn đã liên kết để giải thích rằng một loại không rõ ràng nếu nó không xuất hiện bên phải của ràng buộc, trừ khi ràng buộc đó là sử dụng các phụ thuộc chức năng để suy ra loại không rõ ràng khác với các loại không mơ hồ khác. Vì vậy, hãy so sánh bối cảnh của hai chức năng và tìm kiếm các phụ thuộc chức năng.

class ApplySym sig f sym | sig sym -> f, f -> sig sym
class SyntacticN f internal | f -> internal

sugarSym :: ( sub :<: AST sup
            , ApplySym sig fi sup
            , SyntacticN f fi
            ) 
         => sub sig -> f

share :: ( Let :<: sup
         , sup ~ Domain b
         , sup ~ Domain a
         , Syntactic a
         , Syntactic b
         , Syntactic (a -> b)
         , SyntacticN (a -> (a -> b) -> b) fi
         )
      => a -> (a -> b) -> b

Vì vậy, đối với sugarSymcác loại không mơ hồ sub, sigf, từ các loại chúng ta sẽ có thể tuân theo các phụ thuộc chức năng để phân tán tất cả các loại khác được sử dụng trong ngữ cảnh, cụ thể là supfi. Và thực tế, sự f -> internalphụ thuộc chức năng trong việc SyntacticNsử dụng của chúng tôi fđể định hướng của chúng tôi fi, và sau đó sự f -> sig symphụ thuộc chức năng trong việc ApplySymsử dụng chúng tôi mới định hướng fiđể định hướng sup(và sig, điều này đã không mơ hồ). Vì vậy, điều đó giải thích tại sao sugarSymkhông yêu cầu AllowAmbiguousTypesgia hạn.

Bây giờ chúng ta hãy nhìn vào sugar. Điều đầu tiên tôi nhận thấy là trình biên dịch không phàn nàn về một kiểu mơ hồ, mà là về các trường hợp chồng chéo:

Overlapping instances for SyntacticN b fi
  arising from the ambiguity check for share
Matching givens (or their superclasses):
  (SyntacticN (a -> (a -> b) -> b) fi1)
Matching instances:
  instance [overlap ok] (Syntactic f, Domain f ~ sym,
                         fi ~ AST sym (Full (Internal f))) =>
                        SyntacticN f fi
    -- Defined in ‘Data.Syntactic.Sugar’
  instance [overlap ok] (Syntactic a, Domain a ~ sym,
                         ia ~ Internal a, SyntacticN f fi) =>
                        SyntacticN (a -> f) (AST sym (Full ia) -> fi)
    -- Defined in ‘Data.Syntactic.Sugar’
(The choice depends on the instantiation of b, fi’)
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes

Vì vậy, nếu tôi đọc đúng, không phải GHC nghĩ rằng các loại của bạn không rõ ràng, mà là trong khi kiểm tra xem các loại của bạn có mơ hồ hay không, GHC gặp phải một vấn đề riêng biệt khác. Sau đó, bạn sẽ nói với bạn rằng nếu bạn nói với GHC không thực hiện kiểm tra sự mơ hồ, thì nó sẽ không gặp phải vấn đề riêng biệt đó. Điều này giải thích tại sao việc kích hoạt AllowAmbiguptTypes cho phép mã của bạn biên dịch.

Tuy nhiên, vấn đề với các trường hợp chồng chéo vẫn còn. Hai trường hợp được liệt kê bởi GHC ( SyntacticN f fiSyntacticN (a -> f) ...) trùng lặp với nhau. Thật kỳ lạ, có vẻ như lần đầu tiên trong số này sẽ trùng lặp với bất kỳ trường hợp nào khác, điều đáng ngờ. Và nó [overlap ok]có nghĩa là gì?

Tôi nghi ngờ rằng Syntactic được biên dịch với OverlapsInstances. Và nhìn vào , thực sự nó làm.

Thử nghiệm một chút, có vẻ như GHC vẫn ổn với các trường hợp chồng chéo khi rõ ràng rằng cái này hoàn toàn tổng quát hơn cái kia:

{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}

class Foo a where
  whichOne :: a -> String

instance Foo a where
  whichOne _ = "a"

instance Foo [a] where
  whichOne _ = "[a]"

-- |
-- >>> main
-- [a]
main :: IO ()
main = putStrLn $ whichOne (undefined :: [Int])

Nhưng GHC không ổn với các trường hợp chồng chéo khi rõ ràng không phù hợp hơn các trường hợp khác:

{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}

class Foo a where
  whichOne :: a -> String

instance Foo (f Int) where  -- this is the line which changed
  whichOne _ = "f Int"

instance Foo [a] where
  whichOne _ = "[a]"

-- |
-- >>> main
-- Error: Overlapping instances for Foo [Int]
main :: IO ()
main = putStrLn $ whichOne (undefined :: [Int])

Chữ ký loại của bạn sử dụng SyntacticN (a -> (a -> b) -> b) fi, và không phải SyntacticN f ficũng không SyntacticN (a -> f) (AST sym (Full ia) -> fi)phù hợp hơn loại kia. Nếu tôi thay đổi một phần chữ ký loại của bạn thành SyntacticN a fihoặc SyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi), GHC không còn phàn nàn về sự chồng chéo.

Nếu tôi là bạn, tôi sẽ xem xét định nghĩa của hai trường hợp có thể đó và xác định xem một trong hai triển khai đó có phải là điều bạn muốn không.


2

Tôi đã phát hiện ra rằng AllowAmbiguousTypesrất thuận tiện để sử dụng với TypeApplications. Hãy xem xét chức năng natVal :: forall n proxy . KnownNat n => proxy n -> Integertừ GHC.TypeLits .

Để sử dụng chức năng này, tôi có thể viết natVal (Proxy::Proxy5). Một phong cách thay thế là sử dụng TypeApplications: natVal @5 Proxy. Loại Proxyđược suy ra bởi ứng dụng loại và thật khó chịu khi phải viết nó mỗi khi bạn gọi natVal. Do đó chúng ta có thể kích hoạt AmbiguousTypesvà viết:

{-# Language AllowAmbiguousTypes, ScopedTypeVariables, TypeApplications #-}

ambiguousNatVal :: forall n . (KnownNat n) => Integer
ambiguousNatVal = natVal @n Proxy

five = ambiguousNatVal @5 -- no `Proxy ` needed!

Tuy nhiên, lưu ý rằng một khi bạn mơ hồ, bạn không thể quay lại !

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.