Birisi bana bağımlı yazmayı açıklayabilir mi? Haskell, Cayenne, Epigram veya diğer işlevsel dillerde çok az deneyimim var, bu yüzden ne kadar basit terimler kullanırsanız, onu o kadar çok takdir edeceğim!
Birisi bana bağımlı yazmayı açıklayabilir mi? Haskell, Cayenne, Epigram veya diğer işlevsel dillerde çok az deneyimim var, bu yüzden ne kadar basit terimler kullanırsanız, onu o kadar çok takdir edeceğim!
Yanıtlar:
Şunu bir düşünün: tüm uygun programlama dillerinde işlevler yazabilirsiniz, örn.
def f(arg) = result
Burada f
bir değer alır arg
ve bir değer hesaplar result
. Değerlerden değerlere bir fonksiyondur.
Şimdi, bazı diller polimorfik (diğer adıyla genel) değerleri tanımlamanıza izin verir:
def empty<T> = new List<T>()
Burada empty
bir tür alır T
ve bir değer hesaplar. Türlerden değerlere bir işlevdir.
Genellikle, genel tür tanımlarına da sahip olabilirsiniz:
type Matrix<T> = List<List<T>>
Bu tanım bir tür alır ve bir tür döndürür. Türlerden türlere bir işlev olarak görülebilir.
Sıradan dillerin sunduğu şey için çok fazla. Dördüncü olasılık, yani işlevleri değerlerden türlere tanımlama olanağı sunuyorsa, bir dil bağımlı olarak yazılır. Veya başka bir deyişle, bir tür tanımını bir değer üzerinden parametreleştirmek:
type BoundedInt(n) = {i:Int | i<=n}
Bazı ana dillerde bunun karıştırılmaması gereken bazı sahte biçimleri vardır. Örneğin, C ++ 'da, şablonlar değerleri parametre olarak alabilir, ancak uygulandıklarında derleme zamanı sabitleri olmaları gerekir. Gerçekten bağımlı olarak yazılmış bir dilde öyle değil. Örneğin, yukarıdaki türü şu şekilde kullanabilirim:
def min(i : Int, j : Int) : BoundedInt(j) =
if i < j then i else j
Burada, işlevin sonucu türü bağlıdır gerçek argüman değerine j
, dolayısıyla terminoloji.
BoundedInt
Yine de örnek bir Ayrıntılandırma Türü değil mi? Bu 'oldukça yakın', ancak tam olarak 'bağımlı türler' değil, örneğin İdris'in dep.typing hakkında bir eğitimde ilk olarak bahsettiği türden.
Bağımlı türler , derleme sırasında daha büyük mantık hataları kümesinin ortadan kaldırılmasını sağlar . Bunu açıklamak için aşağıdaki fonksiyon özelliklerini dikkate alın :f
İşlev , girdi olarak
f
yalnızca çift tam sayıları almalıdır .
Bağımlı türler olmadan şuna benzer bir şey yapabilirsiniz:
def f(n: Integer) := {
if n mod 2 != 0 then
throw RuntimeException
else
// do something with n
}
Burada derleyici n
gerçekten çift olup olmadığını algılayamaz , yani derleyicinin bakış açısından aşağıdaki ifade uygundur:
f(1) // compiles OK despite being a logic error!
Bu program çalışır ve ardından çalışma zamanında istisna atar, yani programınızda bir mantık hatası vardır.
Şimdi, bağımlı türler çok daha anlamlı olmanızı sağlar ve şuna benzer bir şey yazmanıza olanak tanır:
def f(n: {n: Integer | n mod 2 == 0}) := {
// do something with n
}
İşte n
bağımlı tipte {n: Integer | n mod 2 == 0}
. Bunu yüksek sesle okumak yardımcı olabilir
n
her bir tamsayı 2'ye bölünebilecek şekilde bir tamsayılar kümesinin üyesidir.
Bu durumda derleyici, derleme zamanında tek bir sayı ilettiğiniz bir mantık hatasını algılar f
ve programın ilk etapta çalıştırılmasını engeller:
f(1) // compiler error
İşte Scala yoluna bağlı türleri kullanan açıklayıcı bir örnek , f
böyle bir gereksinimi karşılayan işlevi nasıl uygulamaya çalışabileceğimize dair :
case class Integer(v: Int) {
object IsEven { require(v % 2 == 0) }
object IsOdd { require(v % 2 != 0) }
}
def f(n: Integer)(implicit proof: n.IsEven.type) = {
// do something with n safe in the knowledge it is even
}
val `42` = Integer(42)
implicit val proof42IsEven = `42`.IsEven
val `1` = Integer(1)
implicit val proof1IsOdd = `1`.IsOdd
f(`42`) // OK
f(`1`) // compile-time error
Anahtar, değerin değer n
türünde nasıl göründüğüne dikkat etmektir, proof
yani n.IsEven.type
:
def f(n: Integer)(implicit proof: n.IsEven.type)
^ ^
| |
value value
Biz demek tipi n.IsEven.type
bağlıdır değeri n
dolayısıyla dönem bağımlı-tipleri .
f(random())
derleme hatasıyla sonuçlanır mı?
f
Bir ifadeye başvurmak , derleyicinin (sizin yardımınızla veya yardımınız olmadan) ifadenin her zaman eşit olmasını ve böyle bir kanıtın bulunmadığını random()
(çünkü aslında garip olabileceğinden) f(random())
sağlamasını gerektirir , bu nedenle derleme başarısız olur.
C ++ 'yı biliyorsanız, motive edici bir örnek vermek kolaydır:
Diyelim ki bir kapsayıcı türü ve bunun iki örneği var
typedef std::map<int,int> IIMap;
IIMap foo;
IIMap bar;
ve şu kod parçasını göz önünde bulundurun (foo'nun boş olmadığını varsayabilirsiniz):
IIMap::iterator i = foo.begin();
bar.erase(i);
Bu apaçık bir çöplüktür (ve muhtemelen veri yapılarını bozar), ancak "iteratörden foo'ya" ve "çubuğa yineleyici" aynı türden olduğu için, IIMap::iterator
anlamsal olarak tamamen uyumsuz olsalar bile , yazım denetimi iyi olacaktır .
Sorun, bir yineleyici türünün yalnızca kap türüne değil, aslında kap nesnesine bağlı olması gerektiğidir, yani "statik olmayan üye türü" olmalıdır:
foo.iterator i = foo.begin();
bar.erase(i); // ERROR: bar.iterator argument expected
Böyle bir özellik, bir terime (foo) bağlı olan bir türü (foo.iterator) ifade etme yeteneği, tam olarak bağımlı yazmanın ne anlama geldiğidir.
Bu özelliği sık sık görmemenizin nedeni, büyük bir solucan kutusu açmasıdır: Birdenbire, iki türün aynı olup olmadığını kontrol etmek için derleme sırasında iki ifadeyi kanıtlamak zorunda kalırsınız. eşdeğerdir (çalışma zamanında her zaman aynı değeri verir). Eğer Wikipedia'nın karşılaştırırsanız sonucunda, bağımlı yazılan diller listesini onun ile teorem ispatlayıcılar listesinde Şüpheli benzerlik fark edebilirsiniz. ;-)
Kitap Türleri ve Programlama Dillerinden Alıntı Yapma (30.5):
Bu kitabın çoğu, çeşitli türlerdeki soyutlama mekanizmalarının resmileştirilmesiyle ilgiliydi. Basit tipte lambda-hesaplamasında, bir terimi alma ve bir alt terimi soyutlama işlemini resmileştirdik, daha sonra farklı terimlere uygulayarak somutlaştırılabilecek bir fonksiyon elde ettik. Sistemde
F
, bir terimi alma ve bir türü soyutlama, onu çeşitli türlere uygulayarak somutlaştırılabilen bir terim ortaya çıkarma işlemini düşündük. İçindeλω
, daha sonra farklı türlere uygulayarak somutlaştırılabilecek bir tip operatörü elde etmek için bir tür alıp bir alt ifadeyi soyutlayarak basit tipte lambda-hesap "bir seviye yukarı" mekanizmasını özetledik. Tüm bu soyutlama biçimlerini düşünmenin uygun bir yolu, diğer ifadelerle indekslenen ifade aileleri açısından düşünmektir. Sıradan bir lambda soyutlamasıλx:T1.t2
, terimlere göre[x -> s]t1
indekslenen bir terimler ailesidirs
. Benzer şekilde, bir tür soyutlamasıλX::K1.t2
, türlere göre indekslenen bir terimler ailesidir ve bir tür operatörü, türlere göre indekslenen bir türler ailesidir.
λx:T1.t2
terimlere göre indekslenen terimler ailesi
λX::K1.t2
türlere göre indekslenmiş terimler ailesi
λX::K1.T2
türlere göre indekslenen türler ailesiBu listeye bakıldığında, henüz dikkate almadığımız bir olasılık olduğu açıktır: terimlere göre indekslenmiş tür aileleri. Bu tür soyutlama, bağımlı türler başlığı altında da kapsamlı bir şekilde incelenmiştir.