Yalnızca Hindley-Milner tip sistemini kullanarak bir liste tanımlayın


10

Çalışan bir Hindley-Milner türü çıkarım sistemine sahip küçük bir lambda kalkülüs derleyicisi üzerinde çalışıyorum ve şimdi aynı zamanda Turing'i tamamlamak için yeterli olması gerektiğini anladığım özyinelemeli izinleri de (bağlantılı kodda değil) destekliyorum .

Sorun şu ki, destek listelerini nasıl oluşturacağımı veya bunları destekleyip desteklemediğini bilmiyorum ve bunları kodlamanın bir yolunu bulmam gerekiyor. Yazı sistemine yeni kurallar eklemek zorunda kalmadan bunları tanımlayabilmek istiyorum.

Bir listeyi düşünebilmemin en kolay yolu xya null(ya da boş liste) ya da hem bir hem de bir xlistesini içeren bir çift olarak düşünmektir x. Ancak bunu yapmak için, ürün ve toplam türleri olduğuna inandığım çiftleri ve / veya tanımları yapabilmem gerekiyor.

Çiftleri şu şekilde tanımlayabilirim:

pair = λabf.fab
first = λp.p(λab.a)
second = λp.p(λab.b)

Yana pairtürünü olurdu a -> (b -> ((a -> (b -> x)) -> x)), geçtikten sonra, bir, demek intve bir stringbu tip bir şey verim ediyorum (int -> (string -> x)) -> xbir çift gösterimi olacağını, intve string. Burada beni rahatsız eden şey, eğer bu bir çifti temsil ediyorsa, neden mantıklı olarak önermeye eşdeğer değildir ya da öneriyi ima etmez int and string? Ancak, (((int and string) -> x) -> x)işlevlere parametre olarak yalnızca ürün türlerine sahip olabileceğim gibi. Bu cevapBu sorunu ele alıyor gibi görünüyor, ancak kullandığı sembollerin ne anlama geldiğini bilmiyorum. Ayrıca, bu bir ürün türünü gerçekten kodlamazsa, yukarıdaki çiftler tanımımla yapamadığım ürün türleriyle yapabileceğim bir şey var mı (n-tuples'ı aynı şekilde tanımlayabileceğimi düşünerek)? Değilse, bu (AFAIK) kavuşumunu sadece ima kullanarak ifade edemeyeceğinizle çelişmez miydi?

Ayrıca, toplam türü ne olacak? Bir şekilde sadece fonksiyon tipini kullanarak kodlayabilir miyim? Öyleyse, bu listeleri tanımlamak için yeterli olur mu? Yoksa tür sistemimi genişletmek zorunda kalmadan listeleri tanımlamanın başka bir yolu var mı? Değilse, mümkün olduğunca basit tutmak istersem ne gibi değişiklikler yapmam gerekir?

Lütfen bir bilgisayar programcısı olduğumu, ancak bir bilgisayar bilimcisi ya da bir matematikçi olmadığımı ve matematik notasyonunu okurken oldukça kötü olduğunu unutmayın.

Düzenleme: Şimdiye kadar uyguladıklarımın teknik adının ne olduğundan emin değilim, ama tüm sahip olduğum temelde yukarıda bağladığım kod, uygulamalar, soyutlamalar ve alınan değişkenler için kuralları kullanan bir kısıtlama oluşturma algoritması Hinley-Milner algoritmasından ve ardından ana türü alan bir birleştirme algoritmasından. Örneğin, ifade \a.atürü verir a -> ave ifade \a.(a a)gerçekleşen bir kontrol hatası atar. Bunun da ötesinde, tam olarak bir letkural değil, bu sözde kod gibi özyinelemeli genel işlevleri tanımlamanızı sağlayan aynı etkiye sahip gibi görünen bir işlev vardır:

GetTypeOfGlobalFunction(term, globalScope, nameOfFunction)
{
    // Here 'globalScope' contains a list of name-value pair where every value is of class 'ClosedType', 
    // meaning their type will be cloned before unified in the unification algorithm so that they can be used polymorphically 
    tempType = new TypeVariable() // Assign a dummy type to `tempType`, say, type 'x'.
    // The next line creates an scope with everything in 'globalScope' plus the 'nameOfFunction = tempType' name-value pair
    tempScope = new Scope(globalScope, nameOfFunction, tempType) 
    type = TypeOfTerm(term, tempScope) // Calculate the type of the term 
    Unify(tempType, type)
    return type
    // After returning, the code outside will create a 'ClosedType' using the returned type and add it to the global scope.
}

Kod temelde terimin türünü her zamanki gibi alır, ancak birleştirmeden önce, kukla bir türle tanımlanmış işlevin adını tür kapsamına ekler, böylece kendi içinden özyinelemeli olarak kullanılabilir.

Edit 2: İstediğim gibi bir liste tanımlamak için sahip olmadığım özyinelemeli türlere de ihtiyacım olduğunu fark ettim.


Tam olarak neler uyguladığınız konusunda biraz daha açık olabilir misiniz? Basitçe yazılmış lambda hesabını (özyinelemeli tanımlarla) uyguladınız ve ona parametrik polimorfizmler Hindley-Milner stili verdiniz mi? Yoksa ikinci dereceden polimorfik lambda hesabını uyguladınız mı?
Andrej Bauer

Belki daha kolay bir şekilde sorabilirim: OCaml veya SML alır ve onu saf lambda terimleri ve özyinelemeli tanımlarla kısıtlarsam, neden bahsediyorsunuz?
Andrej Bauer

@AndrejBauer: Soruyu düzenledim. OCaml ve SML hakkında emin değilim, ancak Haskell'i alıp lambda terimleriyle sınırlandırırsanız ve üst düzey özyinelemeli (örneğin let func = \x -> (func x)) sahip olduğum şeyi alırsanız eminim.
Juan

1
Sorunuzu iyileştirmek için bu meta gönderiye göz atın .
Juho

Yanıtlar:


13

Çiftler

Bu kodlama çiftlerin Kilise kodlamasıdır . Benzer teknikler booleanları, tam sayıları, listeleri ve diğer veri yapılarını kodlayabilir.

x:a; y:bpair x y(a -> b -> t) -> t¬

(abt)t¬(¬a¬bt)t(ab¬t)t(ab)t
ab tpairt

pairçift ​​türü için bir kurucudur firstve secondyıkıcıdır. (Bunlar nesne yönelimli programlamada kullanılan kelimelerle aynıdır; burada kelimeler, buraya girmeyeceğim türlerin ve terimlerin mantıksal yorumuyla ilgili bir anlama sahiptir .) Sezgisel olarak, yıkıcılar, nesnede ve yapıcılar, argüman olarak nesnenin bölümlerine uyguladıkları bir işlevi alarak yıkıcıya yol açarlar. Bu ilke diğer türlere de uygulanabilir.

toplamları

Ayrılmış bir birliğin Kilise kodlaması temelde bir çiftin Kilise kodlamasına ikidir. Bir çiftin bir araya getirilmesi gereken iki parçası varsa ve birini veya diğerini çıkarmayı seçebiliyorsanız, birliği iki yoldan biriyle oluşturmayı seçebilirsiniz ve bunu kullandığınızda her iki yöne de izin vermeniz gerekir. Dolayısıyla iki kurucu vardır ve iki argüman alan tek bir yıkıcı vardır.

let case w = λf. λg. w f g           case : ((a->t) -> (b->t) -> t) -> (a->t) -> (b->t) -> t
  (* or simply let case w = w *)
let left x = λf. λg. f x             left : a -> ((a->t) -> (b->t) -> t)
let right y = λf. λg. g x            right : b -> ((a->t) -> (b->t) -> t)

Bana tipini kısaltmak edelim (a->t) -> (b->t) -> tolarak SUM(a,b)(t). O zaman yıkıcı ve yapıcı türleri:

case : SUM(a,b)(t) -> (a->t) -> (b->t) -> t
left : a -> SUM(a,b)(t)
right : b -> SUM(a,b)(t)

Böylece

case (left x) f g → f x
case (rightt y) f g → g y

Listeler

Bir liste için aynı prensibi tekrar uygulayın. Öğeleri türü olan bir liste aiki şekilde oluşturulabilir: boş bir liste olabilir veya bir öğe (kafa) artı bir liste (kuyruk) olabilir. Çiftler ile karşılaştırıldığında, yıkıcılar söz konusu olduğunda küçük bir bükülme vardır: iki ayrı yıkıcıya sahip olamazsınız headve tailsadece boş olmayan listelerde çalışacaklardır. İki argüman içeren tek bir yıkıcıya ihtiyacınız vardır, bunlardan biri nil durumu için 0 argüman işlevi (yani bir değer), diğeri eksileri için 2 argüman işlevi. Fonksiyonlar gibi is_empty, headve tailbu elde edilebilir. Toplamlarda olduğu gibi, liste doğrudan kendi yıkıcı işlevidir.

let nil = λn. λc. n
let cons h t = λn. λc. c h t
let is_empty l = l true (λh. λt. false) 
let head l default = l default (λh. λt. h)
let tail l default = l default (λh. λt. t)

consconsconsTT1,,Tn

Tahmin ettiğiniz gibi, yalnızca homojen listeler içeren bir tür tanımlamak istiyorsanız, özyinelemeli türlere ihtiyacınız vardır. Neden? Bir liste türüne bakalım. Liste, iki bağımsız değişken alan bir işlev olarak kodlanır: boş listelere döndürülecek değer ve eksilerini döndürmek için değeri hesaplayan işlev. Izin vermek aeleman türü, bliste ctürü ve yıkıcı tarafından döndürülen türü olmak. Bir listenin türü

a -> (a -> b -> c) -> c

Listeyi homojen hale getirmek, eğer eksiler hücresi ise, kuyruğun bütünü ile aynı tipte olması gerektiğini, yani kısıtlamayı eklediğini söylüyor.

a -> (a -> b -> c) -> c = b

Hindley-Milner tipi sistem bu tür yinelemeli tiplerle genişletilebilir ve aslında pratik programlama dilleri bunu yapar. Pratik programlama dilleri, bu tür “çıplak” denklemlere izin verme eğilimindedir ve bir veri oluşturucu gerektirir, ancak bu, temel teorinin temel bir şartı değildir. Bir veri oluşturucuya ihtiyaç duyulması, tür çıkarımını basitleştirir ve pratikte, aslında buggy olan ancak işlevin kullanıldığı yerde anlaşılması zor bir tür hatasına neden olan bazı istenmeyen kısıtlamalarla yazılabilen işlevleri kabul etmekten kaçınma eğilimindedir. Bu nedenle, örneğin, OCaml korumasız özyinelemeli türleri yalnızca varsayılan olmayan -rectypesderleyici seçeneğiyle kabul eder . Aşağıda, OCaml sözdiziminde yukarıdaki tanımları ve birliktediğer adlı özyinelemeli türler : type_expression as 'atürün type_expressiondeğişkenle birleştirildiği anlamına gelir 'a.

# let nil = fun n c -> n;;
val nil : 'a -> 'b -> 'a = <fun>
# let cons h t = fun n c -> c h t;;
val cons : 'a -> 'b -> 'c -> ('a -> 'b -> 'd) -> 'd = <fun>
# let is_empty l = l true (fun h t -> false);;
val is_empty : (bool -> ('a -> 'b -> bool) -> 'c) -> 'c = <fun>
# let head l default = l default (fun h t -> h);;
val head : ('a -> ('b -> 'c -> 'b) -> 'd) -> 'a -> 'd = <fun>
# let tail l default = l default (fun h t -> t);;
val tail : ('a -> ('b -> 'c -> 'c) -> 'd) -> 'a -> 'd = <fun>
# type ('a, 'b, 'c) ulist = 'c -> ('a -> 'b -> 'c) -> 'c;;
type ('a, 'b, 'c) ulist = 'c -> ('a -> 'b -> 'c) -> 'c
# is_empty (cons 1 nil);;
- : bool = false
# head (cons 1 nil) 0;;
- : int = 1
# head (tail (cons 1 (cons 2.0 nil)) nil) 0.;;
- : float = 2.

(* -rectypes is required for what follows *)
# type ('a, 'b, 'c) rlist = 'c -> ('a -> 'b -> 'c) -> 'c as 'b;;
type ('a, 'b, 'c) rlist = 'b constraint 'b = 'c -> ('a -> 'b -> 'c) -> 'c
# let rcons = (cons : 'a -> ('a, 'b, 'c) rlist -> ('a, 'b, 'c) rlist);;
val rcons :
  'a ->
  ('a, 'c -> ('a -> 'b -> 'c) -> 'c as 'b, 'c) rlist -> ('a, 'b, 'c) rlist =
  <fun>
# head (rcons 1 (rcons 2 nil)) 0;;
- : int = 1
# tail (rcons 1 (rcons 2 nil)) nil;;
- : 'a -> (int -> 'a -> 'a) -> 'a as 'a = <fun>
# rcons 1 (rcons 2.0 nil);;
Error: This expression has type
         (float, 'b -> (float -> 'a -> 'b) -> 'b as 'a, 'b) rlist = 'a
       but an expression was expected of type
         (int, 'b -> (int -> 'c -> 'b) -> 'b as 'c, 'b) rlist = 'c

kıvrımlar

Buna biraz daha genel olarak baktığımızda, veri yapısını temsil eden işlev nedir?

  • nn
  • (x,y)xy
  • ini(x)ix
  • [x1,,xn]

Genel anlamda, veri yapısı katlama fonksiyonu olarak temsil edilir . Bu, veri yapıları için genel bir kavramdır: katlama işlevi, veri yapısını geçen üst düzey bir işlevdir. Katlamanın evrensel olduğu teknik bir anlam vardır : tüm “jenerik” veri yapısı geçişleri kat olarak ifade edilebilir. Veri yapısının katlama işlevi olarak gösterilebildiğini gösterir: bir veri yapısı hakkında bilmeniz gereken tek şey, onu nasıl geçeceğinizdir, gerisi bir uygulama detayıdır.


Tamsayıların, çiftlerin, toplamların “ Kilise kodlamasından” bahsediyorsunuz , ancak listelerde Scott kodlamasını veriyorsunuz . Bence tümevarım türlerinin kodlarına aşina olmayanlar için biraz kafa karıştırıcı olabilir.
Stéphane Gimenez

Yani temelde, çift tipim aslında bu tür bir işlev olarak bir ürün tipi değil, sadece dönmesi tve alması gereken argümanı görmezden gelebilir ave b(tam olarak ne (a and b) or tdiyor). Görünüşe göre, meblağlarda da aynı sıkıntı yaşıyorum. Ayrıca, özyinelemeli türler olmadan homojen bir listeye sahip olmayacağım. Yani birkaç kelimeyle, homojen listeler almak için toplam, ürün ve özyinelemeli tür kuralları eklemem gerektiğini mi söylüyorsunuz?
Juan

Toplamlar bölümünüzün case (right y) f g → g ysonunda mı demek istediniz ?
Juan

@ StéphaneGimenez Fark etmemiştim. Yazılan bir dünyada bu kodlamalar üzerinde çalışmaya alışkın değilim. Church kodlaması vs Scott kodlaması için bir referans verebilir misiniz?
Gilles 'SO- kötü olmayı bırak'

@JuanLuisSoldi Muhtemelen “fazladan bir dolaylama seviyesiyle çözülemeyen bir sorun yok” sözlerini duymuşsunuzdur. Kilise kodlamaları, bir işlev çağrısı düzeyi ekleyerek veri yapılarını işlevler olarak kodlar: veri yapısı, parçalar üzerinde işlem yapmak için işleve uyguladığınız ikinci dereceden bir işlev haline gelir. Homojen bir liste türü istiyorsanız, kuyruk türünün tüm listenin türüyle aynı olması gerçeğiyle uğraşmak zorunda kalacaksınız. Bunun bir tür özyineleme içermesi gerektiğini düşünüyorum.
Gilles 'SO- kötü olmayı bırak'

2

Toplam türlerini etiket ve değerlere sahip ürün türleri olarak temsil edebilirsiniz. Bu durumda, biraz hile yapabilir ve bir etiketi null değerini göstermek için kullanabiliriz, ikinci etiketi kafa / kuyruk çiftini temsil eder.

Booleanları her zamanki gibi tanımlarız:

true = λi.λe.i
false = λi.λe.e
if = λcond.λthen.λelse.(cond then else)

Daha sonra bir liste ilk elemanı boole ve ikinci elemanı bir kafa / kuyruk çifti olarak içeren bir çifttir. Bazı temel liste işlevleri:

isNull = λl.(first l)
null = pair false false     --The second element doesn't matter in this case
cons = λh.λt.(pair true (pair h t ))
head = λl.(fst (snd l))   --This is a partial function
tail = λl.(snd (snd l))   --This is a partial function  

map = λf.λl.(if (isNull l)
                 null 
                 (cons (f (head l)) (map f (tail l) ) ) 

Ama bu bana homojen bir liste vermezdi, doğru mu?
Juan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.