Kapan -XAllowAmbiguousTypes tepat?

212

Saya baru saja memposting pertanyaan tentang sintaksis-2.0 tentang definisi share. Saya sudah menjalankan ini di 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

Namun, GHC 7.8 ingin -XAllowAmbiguousTypesmengkompilasi dengan tanda tangan itu. Atau, saya bisa mengganti fidengan

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

yang merupakan jenis yang tersirat oleh fundep pada SyntacticN. Ini memungkinkan saya untuk menghindari ekstensi. Tentu ini

  • tipe yang sangat panjang untuk ditambahkan ke tanda tangan yang sudah besar
  • melelahkan untuk diturunkan secara manual
  • tidak perlu karena fundep

Pertanyaan saya adalah:

  1. Apakah ini penggunaan yang dapat diterima -XAllowAmbiguousTypes?
  2. Secara umum, kapan ekstensi ini digunakan? Jawaban di sini menunjukkan "itu hampir tidak pernah merupakan ide yang baik".
  3. Meskipun saya sudah membaca dokumen , saya masih kesulitan memutuskan apakah suatu kendala ambigu atau tidak. Secara khusus, pertimbangkan fungsi ini dari Data.Syntactic.Sugar:

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

    Tampak bagi saya bahwa fi(dan mungkin sup) harus ambigu di sini, tetapi dikompilasi tanpa ekstensi. Mengapa tidak sugarSymambigu share? Karena sharemerupakan aplikasi dari sugarSym, sharesemua kendala datang langsung dari sugarSym.

crockeea
sumber
4
Apakah ada alasan mengapa Anda tidak bisa hanya menggunakan tipe inferensi untuk sugarSym Let, yang merupakan (SyntacticN f (ASTF sup a -> ASTF sup (a -> b) -> ASTF sup b), Let :<: sup) => fdan tidak melibatkan variabel tipe ambigu?
kosmikus
3
@kosmikus Sorrt, butuh waktu lama untuk merespons. Kode ini tidak dapat dikompilasi dengan tanda tangan disimpulkan untuk share, tetapi tidak kompilasi ketika salah satu dari tanda tangan yang disebutkan dalam pertanyaan digunakan. Pertanyaan Anda juga ditanyakan dalam komentar di posting sebelumnya
crockeea
3
Perilaku tidak terdefinisi mungkin bukan istilah yang paling tepat. Sulit dipahami hanya berdasarkan satu program. Masalahnya adalah deciability dan GHCI tidak dapat membuktikan jenis dalam program Anda. Ada diskusi panjang yang mungkin menarik bagi Anda hanya pada subjek ini. haskell.org/pipermail/haskell-cafe/2008-April/041397.html
BlamKiwi
6
Adapun (3), tipe itu tidak ambigu karena Ketergantungan Fungsional dalam definisi SyntacticN (yaitu, f - »fi) dan ApplySym (khususnya, fi -> sig, sup). Dari itu, Anda mendapatkan bahwa fsaja sudah cukup untuk sepenuhnya disambiguate sig, fi, dan sup.
user2141650
3
@ user2141650 Maaf butuh waktu lama untuk membalas. Anda mengatakan fundep pada SyntacticNmerek fiambigu dalam sugarSym, tapi kemudian mengapa hal yang sama tidak berlaku untuk fidi share?
crockeea

Jawaban:

12

Saya tidak melihat versi sintaksis yang diterbitkan yang tanda tangannya untuk sugarSymmenggunakan nama-nama jenis yang tepat, jadi saya akan menggunakan cabang pengembangan di commit 8cfd02 ^ , versi terakhir yang masih menggunakan nama-nama itu.

Jadi, mengapa GHC mengeluh tentang fitanda tangan tipe Anda tetapi bukan untuk sugarSym? Dokumentasi yang Anda tautkan menjelaskan bahwa suatu tipe ambigu jika tidak muncul di sebelah kanan kendala, kecuali jika kendala tersebut menggunakan dependensi fungsional untuk menyimpulkan tipe yang ambigu dari tipe non-ambigu lainnya. Jadi mari kita bandingkan konteks kedua fungsi dan mencari dependensi fungsional.

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

Jadi untuk sugarSym, tipe-tipe non-ambigu adalah sub, sigdan f, dan dari tipe-tipe tersebut kita harus dapat mengikuti dependensi fungsional untuk mendisambiguasikan semua tipe lain yang digunakan dalam konteks, yaitu supdan fi. Dan memang, f -> internalketergantungan fungsional dalam SyntacticNmenggunakan kita funtuk disambiguasi kita fi, dan kemudian f -> sig symketergantungan fungsional dalam ApplySymmenggunakan kita yang baru-disambiguasi fiuntuk disambiguasi sup(dan sig, yang sudah non-ambigu). Jadi itu menjelaskan mengapa sugarSymtidak memerlukan AllowAmbiguousTypesekstensi.

Sekarang mari kita lihat sugar. Hal pertama yang saya perhatikan adalah bahwa kompiler tidak mengeluh tentang tipe yang ambigu, melainkan tentang tumpang tindih contoh:

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

Jadi, jika saya membaca ini dengan benar, bukan karena GHC berpikir bahwa tipe Anda ambigu, melainkan, saat memeriksa apakah tipe Anda ambigu, GHC menghadapi masalah yang berbeda dan terpisah. Ini kemudian memberi tahu Anda bahwa jika Anda memberi tahu GHC untuk tidak melakukan pemeriksaan ambiguitas, itu tidak akan menemui masalah yang terpisah. Ini menjelaskan mengapa mengaktifkan AllowAmbiguousTypes memungkinkan kode Anda dikompilasi.

Namun, masalah dengan instance yang tumpang tindih tetap ada. Dua contoh yang terdaftar oleh GHC ( SyntacticN f fidan SyntacticN (a -> f) ...) saling tumpang tindih. Anehnya, sepertinya yang pertama harus tumpang tindih dengan contoh lain, yang mencurigakan. Dan apa [overlap ok]artinya?

Saya curiga Syntactic dikompilasi dengan OverlappingInstances. Dan melihat kodenya , memang begitu.

Bereksperimen sedikit, tampaknya GHC baik-baik saja dengan contoh yang tumpang tindih ketika jelas bahwa yang satu lebih ketat daripada yang lain:

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

Tetapi GHC tidak baik dengan kasus yang tumpang tindih ketika tidak ada yang jelas lebih cocok dari yang lain:

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

Jenis tanda tangan Anda gunakan SyntacticN (a -> (a -> b) -> b) fi, dan tidak SyntacticN f fijuga SyntacticN (a -> f) (AST sym (Full ia) -> fi)lebih cocok daripada yang lain. Jika saya mengubah bagian dari tanda tangan tipe Anda menjadi SyntacticN a fiatau SyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi), GHC tidak lagi mengeluh tentang tumpang tindih.

Jika saya adalah Anda, saya akan melihat definisi dari dua contoh yang mungkin dan menentukan apakah salah satu dari dua implementasi itu adalah yang Anda inginkan.

gelisam
sumber
2

Saya telah menemukan bahwa AllowAmbiguousTypessangat nyaman untuk digunakan TypeApplications. Pertimbangkan fungsi natVal :: forall n proxy . KnownNat n => proxy n -> Integerdari GHC.TypeLits .

Untuk menggunakan fungsi ini, saya bisa menulis natVal (Proxy::Proxy5). Gaya alternatif adalah untuk digunakan TypeApplications: natVal @5 Proxy. Jenis Proxydisimpulkan oleh aplikasi jenis, dan itu menjengkelkan harus menulisnya setiap kali Anda menelepon natVal. Dengan demikian kita dapat mengaktifkan AmbiguousTypesdan menulis:

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

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

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

Namun, perhatikan bahwa setelah Anda ambigu, Anda tidak dapat kembali !

crockeea
sumber