Haskell'in tip denetleyicisi makul. Sorun şu ki, kullandığınız bir kütüphanenin yazarları ... daha az makul bir şey yapmış.
Kısa cevap: Evet, 10 :: (Float, Float)
bir örnek varsa tamamen geçerlidir Num (Float, Float)
. Derleyicinin veya dilin bakış açısından bunda "çok yanlış" hiçbir şey yok. Sayısal değişmezlerin ne işe yaradığına dair sezgilerimizle uyuşmuyor. Yaptığınız hatayı yakalayan tip sistemine alışkın olduğunuz için, haklı olarak şaşırdınız ve hayal kırıklığına uğradınız!
Num
örnekler ve fromInteger
sorun
Derleyicinin kabul etmesine şaşırıyorsunuz 10 :: Coord
, yani 10 :: (Float, Float)
. Gibi sayısal değişmez değerlerin 10
"sayısal" türlere sahip olduğu sonucuna varmak mantıklıdır . Kutudan, sayısal hazır olarak yorumlanabilir Int
, Integer
, Float
, veya Double
. Başka bir bağlamı olmayan bir dizi sayı, bu dört türün sayı olma biçiminde bir sayı gibi görünmüyor. Hakkında konuşmuyoruz Complex
.
Neyse ki ya da maalesef Haskell çok esnek bir dildir. Standart, bir tamsayı değişmezinin türüne sahip 10
olarak yorumlanacağını belirtir . Dolayısıyla , kendisi için bir örneği yazılmış herhangi bir tür olarak çıkarılabilir . Bunu başka bir cevapta biraz daha detaylı anlatıyorumfromInteger 10
Num a => a
10
Num
.
Dolayısıyla, sorunuzu gönderdiğinizde deneyimli bir Haskeller 10 :: (Float, Float)
, kabul edilmek için Num a => Num (a, a)
veya gibi bir durum olması gerektiğini hemen fark etti Num (Float, Float)
. İçinde böyle bir örnek yok Prelude
, bu yüzden başka bir yerde tanımlanmış olmalı. Kullanarak :i Num
, nereden geldiğini çabucak fark ettiniz:gloss
paket.
Eş anlamlıları ve öksüz örnekleri yazın
Ama bir dakika bekleyin. gloss
Bu örnekte herhangi bir tür kullanmıyorsunuz ; olay gloss
sizi neden etkiledi? Cevap iki adımda geliyor.
İlk olarak, anahtar kelimeyle tanıtılan bir tür eşanlamlısı type
yeni bir tür oluşturmaz . Modülünüzde yazmak Coord
basitçe kısaltmadır (Float, Float)
. Aynı şekilde Graphics.Gloss.Data.Point
, Point
anlamı (Float, Float)
. Başka bir deyişle, sizin Coord
ve gloss
'ler Point
tam anlamıyla eşdeğerdir.
Dolayısıyla, geliştiriciler gloss
yazmayı seçtiklerinde instance Num Point where ...
, sizin Coord
yazınızı da bir örneği yaptılar Num
. Bu instance Num (Float, Float) where ...
veya ile eşdeğerdir instance Num Coord where ...
.
(Varsayılan olarak Haskell, tür eş anlamlılarının sınıf örnekleri olmasına izin vermez. gloss
Yazarlar, bir çift dil uzantısı etkinleştirmelidir TypeSynonymInstances
veFlexibleInstances
örneği yazmak için.)
İkincisi, bu şaşırtıcı çünkü bu bir öksüz örnek , yani instance C A
her ikisinin C
ve A
diğer modüllerde tanımlandığı bir örnek bildirimi . Burada her parçası dahil çünkü yani özellikle ayırma sahasında Num
, (,)
ve Float
gelir, Prelude
ve her yerde kapsamında olması muhtemeldir.
Beklentiniz, içinde Num
tanımlanandır Prelude
, tuples ve Float
tanımlanır Prelude
, bu nedenle bu üç şeyin nasıl çalıştığına dair her şey,Prelude
. Tamamen farklı bir modülü içe aktarmak neden herhangi bir şeyi değiştirsin? İdeal olarak olmaz, ancak öksüz örnekler bu sezgiyi kırar.
(GHC'nin öksüz örnekler hakkında uyardığını unutmayın - yazarları gloss
özellikle bu uyarıyı geçersiz kılar. Bu, bir kırmızı bayrak kaldırmalı ve en azından belgelerde bir uyarı vermelidir.)
Sınıf örnekleri geneldir ve gizlenemez
Ayrıca, sınıf örnekleri olan küresel : geçişli ithal herhangi modülünde tanımlanmış herhangi bir oluşum için örnek çözünürlüğünü yaparken typechecker için bağlam ve mevcut olacaktır modülü. Bu, genel muhakemeyi uygun hale getirir, çünkü (genellikle) bir sınıf işlevinin (+)
belirli bir tür için her zaman aynı olacağını varsayabiliriz . Ancak, yerel kararların küresel etkileri olduğu anlamına da gelir; Bir sınıf örneğini tanımlamak, aşağı akış kodunun bağlamını geri alınamaz bir şekilde değiştirir, onu modül sınırlarının arkasına gizlemenin veya maskelemenin bir yolu yoktur.
Sen örneklerini içe kaçınmak önemlidir listelerini kullanamazsınız . Benzer şekilde, tanımladığınız modüllerden örnekleri dışa aktarmayı engelleyemezsiniz.
Bu, Haskell dil tasarımının sorunlu ve çok tartışılan bir alanıdır. Bu reddit başlığında ilgili konularla ilgili büyüleyici bir tartışma var . Örneğin, Edward Kmett'in örnekler için görünürlük kontrolüne izin verme konusundaki yorumuna bakın: "Temelde yazdığım kodun neredeyse tamamının doğruluğunu atıyorsunuz."
(Aynı Bu arada, bu yanıt gösterdi , sen yapabilirsiniz yetim örneklerini kullanarak bazı açısından küresel örnekli varsayımını kırmak!)
Ne yapmalı - kütüphane uygulayıcıları için
Uygulamadan önce iki kez düşünün Num
. Sen geçici bir çözüm olamaz fromInteger
sorunu-hayır, tanımlama fromInteger = error "not implemented"
yok değil daha iyi yapmak. Tam sayı gerçek değerlerinin yanlışlıkla örneklediğiniz türe sahip olduğu sonucuna varılırsa, kullanıcılarınızın kafası karışacak veya şaşıracak mı - ya da daha kötüsü, asla fark etmeyecek mi? Sağlamak (*)
ve (+)
bu kritik mi - özellikle de hacklemeniz gerekiyorsa?
Conal Elliott adlı gibi bir kütüphanede tanımlı alternatif aritmetik operatörler kullanmayı düşünün vector-space
(tür türleri için *
veya Edward Kmett en) linear
(tür tipleri için * -> *
). Kendi kendime yapma eğilimim bu.
Kullanın -Wall
. Yetim örnekleri uygulamayın ve öksüz örnek uyarısını devre dışı bırakmayın.
Alternatif olarak, linear
iyi huylu kitaplıkların liderliğini ve diğer pek çok kütüphaneyi takip edin ve .OrphanInstances
veya ile biten ayrı bir modülde öksüz örnekler sağlayın .Instances
. Ve bu modülü başka herhangi bir modülden içe aktarmayın . Daha sonra kullanıcılar isterlerse yetimleri açıkça ithal edebilirler.
Kendinizi öksüzleri tanımlarken bulursanız, mümkün ve uygunsa, üst düzey bakıcılardan bunları uygulamalarını istemeyi düşünün. Yetim örneğini Show a => Show (Identity a)
onlar ekleyene kadar sık sık yazardım transformers
. Hatta bununla ilgili bir hata raporu hazırlamış olabilirim; Ben hatırlamıyorum
Ne yapmalı - kütüphane tüketicileri için
Çok seçeneğiniz yok. Kütüphane sorumlularına - kibarca ve yapıcı bir şekilde! - ulaşın. Onları bu soruya yönlendirin. Sorunlu yetimi yazmak için özel bir nedenleri olabilir veya farkına varmamış olabilirler.
Daha genel olarak: Bu olasılığın farkında olun. Bu, Haskell'in gerçek küresel etkilerin olduğu birkaç alandan biridir; İçe aktardığınız her modülün ve bu modüllerin içe aktardığı her modülün artık örnekleri uygulamadığını kontrol etmeniz gerekir . Tip açıklamaları bazen sizi sorunlara karşı uyarabilir ve tabii ki :i
kontrol etmek için GHCi'de kullanabilirsiniz.
Yeterince önemliyse eşanlamlı kelimeler newtype
yerine kendi sözcüklerinizi tanımlayın type
. Onlara kimsenin bulaşmayacağından oldukça emin olabilirsiniz.
Açık kaynaklı bir kitaplıktan sık sık sorun yaşıyorsanız, elbette kitaplığın kendi sürümünüzü oluşturabilirsiniz, ancak bakım çabucak baş ağrısına dönüşebilir.