เมื่อ -XAllowAmbiguousTypes เหมาะสมเมื่อใด


212

ผมเคยโพสต์เมื่อเร็ว ๆ นี้คำถามเกี่ยวกับประโยค-2.0shareเกี่ยวกับความหมายของ ฉันเคยทำงานใน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

อย่างไรก็ตาม GHC 7.8 ต้องการ-XAllowAmbiguousTypesรวบรวมด้วยลายเซ็นดังกล่าว หรือฉันสามารถแทนที่fiด้วย

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

ซึ่งเป็นชนิดโดยนัย fundep SyntacticNบน สิ่งนี้ทำให้ฉันสามารถหลีกเลี่ยงการขยาย แน่นอนว่านี่คือ

  • ชนิดที่ยาวมากเพื่อเพิ่มลายเซ็นที่มีขนาดใหญ่อยู่แล้ว
  • น่าเบื่อที่จะได้รับด้วยตนเอง
  • ไม่จำเป็นเนื่องจากเงินทุน

คำถามของฉันคือ:

  1. นี่เป็นการใช้งานที่ยอมรับได้-XAllowAmbiguousTypesหรือไม่?
  2. โดยทั่วไปควรใช้ส่วนขยายนี้เมื่อใด คำตอบที่นี่ชี้ให้เห็นว่า
  3. แม้ว่าฉันจะอ่านเอกสารฉันยังคงมีปัญหาในการตัดสินใจว่าข้อ จำกัด คลุมเครือหรือไม่ พิจารณาฟังก์ชันนี้จาก Data.Syntactic.Sugar โดยเฉพาะ:

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

    ดูเหมือนว่าฉันfi(และอาจsup) ควรจะคลุมเครือที่นี่ แต่มันรวบรวมโดยไม่มีการขยาย ทำไมไม่มีความsugarSymชัดเจนในขณะที่share? เนื่องจากshareเป็นโปรแกรมของsugarSymการshareจำกัด sugarSymทั้งหมดมาตรงจาก


4
มีเหตุผลใดบ้างที่คุณไม่สามารถใช้ประเภทอนุมานเพื่อsugarSym Letซึ่งเป็น(SyntacticN f (ASTF sup a -> ASTF sup (a -> b) -> ASTF sup b), Let :<: sup) => fและไม่เกี่ยวข้องกับตัวแปรประเภทที่ไม่ชัดเจน?
kosmikus

3
@kosmikus Sorrt ใช้เวลานานกว่าจะตอบกลับ รหัสนี้ไม่ได้รวบรวมลายเซ็นที่อนุมานไว้shareแต่จะรวบรวมเมื่อมีการใช้ลายเซ็นที่กล่าวถึงในคำถามอย่างใดอย่างหนึ่ง คำถามของคุณยังถูกถามในความคิดเห็นของโพสต์ก่อนหน้านี้
crockeea

3
พฤติกรรมที่ไม่ได้กำหนดอาจไม่ใช่คำที่เหมาะสมที่สุด มันยากที่จะเข้าใจเพียงแค่ใช้โปรแกรมเดียว ปัญหาคือความน่าเชื่อถือและ GHCI ไม่สามารถพิสูจน์ประเภทในโปรแกรมของคุณได้ มีการพูดคุยกันยาวนานที่คุณอาจสนใจในหัวข้อนี้ haskell.org/pipermail/haskell-cafe/2008-April/041397.html
BlamKiwi

6
สำหรับ (3) ชนิดนั้นไม่ชัดเจนเนื่องจากการพึ่งพาการทำงานในคำจำกัดความของ SyntacticN (เช่น f - » fi) และ ApplySym (โดยเฉพาะ fi -> sig, sup) จากที่คุณได้รับที่fอยู่คนเดียวก็เพียงพอในการรองรับการกระจ่างsig, และfi sup
user2141650

3
@ user2141650 ขออภัยที่ใช้เวลานานในการตอบ คุณกำลังบอกว่าการระดมทุนในSyntacticNทำให้fiชัดเจนในsugarSymแต่ทำไมเหมือนกันไม่เป็นเช่นนั้นสำหรับfiในshare?
crockeea

คำตอบ:


12

ฉันไม่เห็น syntactic รุ่นที่ตีพิมพ์ใด ๆ ที่มีลายเซ็นสำหรับsugarSymใช้ชื่อประเภทที่แน่นอนเหล่านั้นดังนั้นฉันจะใช้สาขาการพัฒนาที่ commit 8cfd02 ^เวอร์ชันสุดท้ายที่ยังคงใช้ชื่อเหล่านั้น

แล้วทำไม GHC ถึงบ่นเกี่ยวกับfiลายเซ็นในแบบของคุณ แต่ไม่ใช่แบบนั้นsugarSym? เอกสารที่คุณเชื่อมโยงเพื่ออธิบายว่าประเภทนั้นคลุมเครือหากไม่ปรากฏทางด้านขวาของข้อ จำกัด เว้นแต่ข้อ จำกัด นั้นใช้การพึ่งพาการทำงานเพื่ออนุมานชนิดที่ไม่ชัดเจนจากประเภทที่ไม่ชัดเจนอื่น ๆ ลองเปรียบเทียบบริบทของทั้งสองฟังก์ชั่นและค้นหาการพึ่งพาการทำงาน

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

ดังนั้นสำหรับsugarSymประเภทที่ไม่ใช่คลุมเครืออยู่sub, sigและfและจากคนที่เราควรจะสามารถที่จะปฏิบัติตามการอ้างอิงในการทำงานเพื่อให้กระจ่างทุกประเภทที่อื่น ๆ ที่ใช้ในบริบทคือและsup fiและแท้จริงแล้วการf -> internalพึ่งพาฟังก์ชั่นในการSyntacticNใช้งานของเราในการfทำให้กระจ่างของเราfiและหลังจากนั้นการf -> sig symพึ่งพาการทำงานในการApplySymใช้งานของเราใหม่ disambiguated fiเพื่อ disambiguate sup(และsigซึ่งก็ไม่ชัดเจน) เพื่ออธิบายว่าทำไมsugarSymไม่ต้องการAllowAmbiguousTypesส่วนขยาย

sugarตอนนี้ขอให้ดูที่ สิ่งแรกที่ฉันสังเกตได้คือคอมไพเลอร์ไม่ได้บ่นเกี่ยวกับประเภทที่คลุมเครือ แต่เกี่ยวกับอินสแตนซ์ที่ทับซ้อนกัน:

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

ดังนั้นหากฉันกำลังอ่านสิ่งนี้ถูกต้องไม่ใช่ GHC คิดว่าประเภทของคุณไม่ชัดเจน แต่แทนที่จะตรวจสอบว่าประเภทของคุณไม่ชัดเจนหรือไม่ GHC พบปัญหาแยกต่างหาก จากนั้นจะบอกคุณว่าหากคุณบอกให้ GHC ไม่ทำการตรวจสอบความกำกวมก็จะไม่พบปัญหาแยกต่างหาก สิ่งนี้อธิบายว่าทำไมการเปิดใช้งาน AllowAmbiguousTypes ทำให้โค้ดของคุณรวบรวม

อย่างไรก็ตามปัญหาของอินสแตนซ์ที่ทับซ้อนกันยังคงอยู่ อินสแตนซ์สองรายการที่ระบุโดย GHC ( SyntacticN f fiและSyntacticN (a -> f) ...) ทำทับซ้อนกัน น่าแปลกที่ดูเหมือนว่าครั้งแรกของสิ่งเหล่านี้จะทับซ้อนกับอินสแตนซ์อื่น ๆ ที่น่าสงสัย และ[overlap ok]หมายความว่าอย่างไร

ฉันสงสัยว่า Syntactic ถูกคอมไพล์ด้วย OverlappingInstance และดูที่รหัสแน่นอนมันทำ

จากการทดลองเล็กน้อยดูเหมือนว่า GHC นั้นโอเคกับอินสแตนซ์ที่ทับซ้อนกันเมื่อเห็นได้ชัดว่า GHC นั้นมีความทั่วไปที่เข้มงวดกว่าอีกอันหนึ่ง:

{-# 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])

แต่ GHC นั้นไม่เป็นไรกับอินสแตนซ์ที่ทับซ้อนกันเมื่อไม่เหมาะสมอย่างชัดเจนกว่าแบบอื่น:

{-# 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])

การใช้ลายเซ็นของคุณชนิดSyntacticN (a -> (a -> b) -> b) fiและค่าSyntacticN f fiมิได้SyntacticN (a -> f) (AST sym (Full ia) -> fi)เป็นแบบที่ดีขึ้นกว่าที่อื่น ๆ หากฉันเปลี่ยนส่วนหนึ่งของประเภทลายเซ็นของคุณเป็นSyntacticN a fiหรือSyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi)GHC จะไม่บ่นเกี่ยวกับการทับซ้อนอีกต่อไป

หากฉันเป็นคุณฉันจะดูคำจำกัดความของอินสแตนซ์ที่เป็นไปได้ทั้งสองนั้นและพิจารณาว่าการใช้งานหนึ่งในสองอย่างนั้นเป็นสิ่งที่คุณต้องการหรือไม่


2

ฉันได้ค้นพบว่ามีความสะดวกมากสำหรับใช้กับAllowAmbiguousTypes TypeApplicationsพิจารณาฟังก์ชันnatVal :: forall n proxy . KnownNat n => proxy n -> IntegerจากGHC.TypeLits

ในการใช้ฟังก์ชั่นนี้ฉันสามารถเขียนnatVal (Proxy::Proxy5)ได้ สไตล์สำรองคือการใช้TypeApplications: natVal @5 Proxy. ประเภทของการอนุมานได้โดยการประยุกต์ใช้ชนิดและเป็นที่น่ารำคาญที่จะต้องมีการเขียนมันทุกครั้งที่คุณโทรProxy natValดังนั้นเราสามารถเปิดใช้งานAmbiguousTypesและเขียน:

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

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

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

อย่างไรก็ตามโปรดทราบว่าเมื่อคุณคลุมเครือคุณจะไม่สามารถย้อนกลับได้ !

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.