2015-04-08 7 views
11

Aşağıdaki örneği inceleyelim:GHC, birden fazla kapsam içerdiğinde hangi sözlük seçilir?

import Data.Constraint 

class Bar a where 
    bar :: a -> a 

foo :: (Bar a) => Dict (Bar a) -> a -> a 
foo Dict = bar 

GHC foo bir Bar örneği seçerken sözlüğü kullanmak için iki seçenek vardır: foo üzerine Bar a kısıtından sözlük kullanmak olabilir, ya da çalışma zamanını kullanabilirsiniz Dict sözlük almak için Sözlüklerin farklı örneğine karşılık geldiği bir örnek için bkz. this question.

GHC hangi sözlükleri kullanır ve neden bu "doğru" seçimdir?

cevap

5

GHC sadece birini seçer ve bu doğru bir seçimdir aynı kısıtı için herhangi iki sözlükleri eşit olması gerekiyordu

OverlappingInstances ve IncoherentInstances tahrip gücünün temelde eşittir;. Ikisi de örneğini kaybetmek tasarımla tutarlılık (programınızdaki her iki eşit kısıtlama, aynı sözlük tarafından karşılanır) Üst üste binme örnekleri, duruma göre hangi örneklerin kullanılacağını öğrenmek için biraz daha fazla yetenek sağlar, ancak bu işe yarar değildir. Dicts'i birinci sınıf değerler olarak geçme noktasına geldiğinizde, örtüşen örnekleri geniş ölçüde eşdeğer olarak kabul ettiğimde sadece örtüşen Değerleri kullanmayı düşünürdüm (ör., Int) gibi belirli bir tür için daha verimli ancak başka bir şekilde eşit bir uygulama, ancak o zaman bile, eğer bu özel uygulamayı yazmak için performans hakkında yeterince dikkatli olursam, eğer böyle bir duruma geldiğinde kullanılmazsa bir performans hatası değildir. ?

Kısaca, OverlappingInstances'ı kullanırsanız, burada hangi sözlüğün seçileceğini sorma hakkından vazgeçersiniz.

Artık, çakışmayı OverlappingInstances olmadan kesebildiğiniz doğru. Aslında, yetimler olmadan ve FlexibleInstances dışında herhangi bir uzantı olmadan da yapabilirsiniz (problem, EsnekInstances etkinleştirildiğinde "yetim" tanımının yanlış olmasıdır). Bu, kısmen düzeltilmemiş çok uzun süredir devam eden bir GHC hatasıdır, çünkü (a) aslında herkesin bildiği kadarıyla çökmelere sebep olamayacağı ve (b) birçok program olabileceği için aslında programın ayrı bölümlerinde aynı kısıtlama için birden çok örneğe sahip olmaktan ve kaçınılması zor olabilir. Temel konuya dönersek, prensip olarak, GHC'nin bir kısıtlamayı sağlamak için mevcut olan herhangi bir sözlüğü seçebilmesi önemlidir, çünkü bunların eşit olmaları gerekse bile, GHC'nin bazılarında daha fazla statik bilgiye sahip olabilir. Page 18 diğerlerinden daha. Örneğiniz açıklayıcı olmak için biraz basittir, ancak bir argümanı bar; Genel olarak GHC, Dict aracılığıyla aktarılan sözlük hakkında hiçbir şey bilmiyor, bu yüzden bilinmeyen bir işleve çağrı olarak bakmak zorundasınız, ancak () kapsamındaki foo örneğini kapsamaktasınız. Bar a kısıtlama sözlüğünden gelen bar'un T 'un bar olduğunu ve bilinen bir işlev için bir çağrı üretebildiğini ve potansiyel olarak T' bar 'u ve sonuç olarak daha fazla optimizasyon gerçekleştirdiğini bilir.

Uygulamada, GHC şu anda bu kadar akıllı değil ve mevcut en içteki sözlüğü kullanıyor. Her zaman en uzak sözlüğü kullanmak zaten daha iyi olurdu. Ancak, birden fazla sözlük bulunan yerlerde bunun gibi durumlar çok yaygın değildir, bu nedenle test etmek için iyi bir ölçütümüz yoktur.

+1

Neden sadece iki uzantıya sahip olduğumuzu (tutarsız/örtüşüyor) değil bir. Tek başına üst üste binmenin hala "güvenli" olduğu izlenimi edinilebilir, ve sadece gerçek olanın başlamasıyla tutarsızlık başlar. – chi

+0

"Aynı kısıtlama için iki sözlükte eşit olması gerekiyordu." - Olması gerekiyordu, ama değil. Bu iddiayı ihlal eden örneklerden, tutarsız veya çakışan örneklerden birini kullanmadan yanıtımı inceleyin. –

+0

Evet, örnek uyum kaybıyla kastediyorum. –

6

Sadece bir tane seçer. Bu doğru seçim değil; oldukça iyi bilinen bir siğil. Çökmelere bu şekilde sebep olabilirsiniz, bu yüzden oldukça kötü bir durumdur.

-- file Class.hs 
{-# LANGUAGE GADTs #-} 
module Class where 

data Dict a where 
    Dict :: C a => Dict a 

class C a where 
    test :: a -> Bool 

-- file A.hs 
module A where 

import Class 

instance C Int where 
    test _ = True 

v :: Dict Int 
v = Dict 

-- file B.hs 
module B where 

import Class 

instance C Int where 
    test _ = False 

f :: Dict Int -> Bool 
f Dict = test (0 :: Int) 

-- file Main.hs 
import TestA 
import TestB 

main = print (f v) 

Hatta Main.hs gayet derler bulmak ve çalışacağı: Burada aynı anda kapsamında iki farklı örneğine sahip olmasını mümkün olduğunu ortaya koymaktadır GADTs başka bir şey kullanarak kısa bir örnektir. Makinemde True numaralı telefonu GHC 7.10.1 ile yazdırıyor, ancak bu kararlı bir sonuç değil. Bunu bir kazaya çevirmek okuyucuya bırakılır.

{-# LANGUAGE FlexibleInstances, OverlappingInstances, IncoherentInstances #-} 
import Data.Constraint 

class C a where foo :: a -> String 
instance C [a] where foo _ = "[a]" 
instance C [()] where foo _ = "[()]" 

aDict :: Dict (C [a]) 
aDict = Dict 

bDict :: Dict (C [()]) 
bDict = Dict 

bar1 :: String 
bar1 = case (bDict, aDict :: Dict (C [()])) of 
     (Dict,Dict) -> foo [()]    -- output: "[a]" 

bar2 :: String 
bar2 = case (aDict :: Dict (C [()]), bDict) of 
     (Dict,Dict) -> foo [()]    -- output: "[()]" 

GHC yukarıdaki kapsam getirildi "son" sözlük kullanmak olur:

+2

Bunun için bir bilet var mı? – crockeea

+2

Bu konuda çökmelere nasıl yol açabileceğinizi açıklayabilir misiniz? – chi

+0

@chi Kapsam kapsamında iki farklı örneği nasıl alacağınızı gösteren bir pasaj verdim. Oradan hiç de zor değil - sadece kodunuzda tüm örneklerin eşit olduğu bazı varsayımlar yapın ve kendinize bir sorun var. –

5

İşte bir test. Yine de buna güvenmeyeceğim. Eğer örtüşen durumlarda kendinizi sınırlamak durumunda

, sadece, o zaman (bildiğim kadarıyla gördüğünüz gibi) aynı tip kapsamında iki farklı sözlükleri getirmek mümkün olmaz ve her şey bu yana iyi olmalı sözlük seçimi önemsiz hale gelir. Ancak, eşgörünümlü örnekleri, başka bir canavara sahiptir, çünkü bunlar bir genel örneğe bağlı kalmanıza izin verir ve daha özel bir örneğe sahip olan bir türde kullanırlar. Bu hangi örneğin kullanılacağını anlamak çok zor. Kısacası, tutarsız örneklerin kötülükleri vardır.


Güncelleme: Başka testler yaptım. Yalnızca üst üste binen örnekleri ve ayrı bir modülde bir yetim örneğini kullanarak, aynı tür için iki farklı sözlükte bulabilirsiniz. Yani, daha fazla uyarıya ihtiyacımız var. . :-(

+0

IncoherentInstances'ın her türlü nastüre yol açabileceğinin farkındayım, bu yüzden benim örneğim * kullanmıyor * (ancak OverlappingInstances ve başka bir modülde bir yetim örneğini kullanıyor). 'OverlappingInstances' undefined davranışa da yol açabileceğinin farkında değildim ... "Güvenli" olduğunu düşündüm. – crockeea

+0

@Eric Ben de öyle düşündüm. Belki bir böcek olarak düşünülebilir ama ben bu konuda uzman değilim. – chi

+0

@Eric Kayıt için, yalnızca örtüşen örnekleri kullanarak "Dict" ın bir modülde "en iyi" örneği oluşturmasını sağlayabilirim: GHC, tek bir en iyi seçenek olduğundan "tutarsız" uzantı olmadan da bunu taahhüt eder. Başka bir modülde bunu ithal ediyorum ve daha iyi bir örnek tanımladım (buna izin verilmemelidir, IMHO). – chi