Kullanımlardan bağlayıcılara bir işlevle ilişkili değişkenleri temsil etme


11

Sözdiziminde ve özellikle yakalamadan kaçınma ikamesinde bağlı değişkenleri temsil etme sorunu iyi bilinir ve birtakım çözümlere sahiptir: alfa denkliğine sahip değişkenler, de Bruijn indeksleri, yerel olarak adsızlık, nominal kümeler vb.

Ama yine de hiçbir yerde kullanılmadığını gördüğüm oldukça açık bir yaklaşım var gibi görünüyor. Yani, temel sözdiziminde tek bir "değişken" terimimiz var, say yazıyor ve sonra ayrı ayrı, her bir değişkeni kapsamı içinde olan bir bağlayıcıyla eşleştiren bir işlev veriyoruz. Yani bir -term gibiλλ

λx.(λy.xy)

yazılır ve işlev, ilk \ madde işaretini ilk \ lambda'ya ve ikinci \ madde işaretini ikinci \ lambda'ya eşler . Bu yüzden de Bruijn endeksleri gibi, sadece karşılık gelen bağlayıcıyı bulmak için terimden çıkarken "say \ lambda s" yerine, bir işlevi değerlendirirsiniz. (Bunu bir uygulamada bir veri yapısı olarak temsil edersem, her değişken terim nesnesini karşılık gelen cilt terim nesnesine basit bir işaretçi / referansla donatmayı düşünürüm.)λ.(λ.)λλλ

Açıkçası bu, insanların okuması için bir sayfaya sözdizimi yazmak için mantıklı değildir, ancak ikisi de de Bruijn endeksleri değildir. Bana öyle geliyor ki, matematiksel olarak mükemmel bir anlam ifade ediyor ve özellikle yakalamadan kaçınmayı ikame etmeyi çok kolay hale getiriyor: sadece yerine koyduğunuz terimi bırakın ve bağlayıcı fonksiyonların birleşimini alın. "Serbest değişken" kavramına sahip olmadığı doğrudur, ama sonradan (yine) de Bruijn endeksleri gerçekten de yoktur; her iki durumda da, serbest değişkenleri içeren bir terim, önünde "bağlam" bağlayıcıları olan bir terimi temsil eder.

Bir şey mi kaçırıyorum ve bu temsilin işe yaramamasının bir nedeni var mı? Diğerlerinden çok daha kötü hale getiren problemler var mı? (Şu anda düşünebildiğim tek sorun, terimlerin (bağlayıcı işlevleriyle birlikte) indüktif olarak tanımlanmadığı, ancak bu aşılamaz görünmüyor olması.) Yoksa aslında kullanıldığı yerler var mı?


2
Sakıncaları bilmiyorum. Belki resmileştirme (örneğin bir kanıt asistanında) daha ağırdır? Emin değilim ... Bildiğim şey, teknik olarak yanlış bir şey olmadığıdır: lambda terimlerini görmenin bu yolu, kanıt ağları olarak gösterilmeleri tarafından önerilen yöntemdir, bu nedenle kanıt bilincine sahip insanlar (benim gibi) örtülü olarak kullanırlar. her zaman. Ama kanıtı net farkında insanlar çok nadirdir :-) Belki de bu gerçekten bir gelenek meselesidir. Not: Soruyu daha görünür hale getirmek için birkaç gevşek etiket ekledim (umarım).
Damiano Mazza

Bu yaklaşım daha üst düzey soyut sözdizimine eşdeğer değil mi (yani, bağlayıcıları ana bilgisayar dilinde işlevler olarak temsil ediyor)? Bir anlamda, bir fonksiyonun bir bağlayıcı olarak kullanılması, kapakların temsilinde dolaylı olarak bağlayıcılara işaretçiler oluşturur.
Rodolphe Lepigre

2
@RodolpheLepigre Ben öyle düşünmüyorum. Özellikle, anlayışım HOAS'ın sadece metatheory oldukça zayıf olduğu zaman doğru olduğu, ancak bu yaklaşımın keyfi bir metatheory'de doğru olduğu yönündedir.
Mike Shulman

3
Doğru, böylece her bağlayıcı benzersiz (ağaç içinde) değişken adı kullanır (ona işaretçi otomatik olarak birdir). Bu Barendregt sözleşmesidir. Ancak yerine koyduğunuzda, benzersiz adlara sahip olmaya devam etmek için yerine koyduğunuz şeyi (C olarak) yeniden oluşturmanız gerekir. Aksi takdirde (genel olarak) birden fazla alt ağaç için aynı işaretçileri kullanırsınız ve değişken yakalama elde edebilirsiniz. Yeniden oluşturma alfa yeniden adlandırılıyor. Muhtemelen, ağaçların kümeler olarak kodlanmasının özelliklerine bağlı olarak benzer bir şey olur mu?
Dan Doel

3
@DanDoel Ah, ilginç. Ben onun yerine ikame ediliyor değişkenin her oluşumunda yerine geçmekte terimin ayrı bir kopyasında düşeceğini söz gerek yoktu açık olduğunu düşündüm ; aksi halde artık bir sözdizimi ağacınız olmazdı ! Bu kopyalamayı alfa yeniden adlandırma olarak düşünmek bana gelmedi, ama şimdi işaret ettiğine göre onu görebiliyorum.
Mike Shulman

Yanıtlar:


11

Andrej ve Łukasz'ın cevapları iyi puan veriyor, ancak ek yorumlar eklemek istedim.

Damiano'nun söylediklerini yankılamak için, işaretçileri kullanarak bağlanmayı temsil etmenin bu yolu kanıt ağları tarafından önerilen yöntemdir, ancak lambda terimleri için gördüğüm en erken yer Knuth tarafından eski bir denemeydi:

  • Donald Knuth (1970). Biçimsel semantik örnekleri. Gelen algoritmik Diller Semantik Sempozyumu , E. Engeler (ed.), Matematik 188, Springer notları.

234 Sayfasında, terimini temsil eden aşağıdaki diyagramı ("bilgi yapısı" olarak adlandırmıştır) :(λy.λz.yz)x

Knuth'un $ (\ lambda y. \ Lambda z.yz) x $ için diyagramı

Lambda terimlerinin bu tür grafik temsili, 1970'lerin başında iki tezde , hem Christopher Wadsworth (1971, Lambda-Calculus'un Anlambilimi ve Edimbilimi ) hem de Richard Statman (1974, Yapısal Karmaşıklık ) tarafından bağımsız olarak (ve daha derinden) incelenmiştir. Kanıtlar ). Günümüzde, bu tür diyagramlara genellikle "λ-grafikleri" denir (örneğin bu makaleye bakınız ).

Knuth'un şemasındaki terimin doğrusal olduğunu gözlemleyin, yani her serbest veya bağlı değişken tam olarak bir kez gerçekleşir - diğerlerinin de belirttiği gibi, bu tür bir temsili - doğrusal terimler.

Öte yandan, doğrusal terimler için bence harika! Doğrusallık, kopyalama ihtiyacını ortadan kaldırır ve böylece hem " eşdeğerliği" ve "ikame" ücretsiz olarak "elde edilir. Bunlar HOAS ile aynı avantajlardır ve aslında Rodolphe Lepigre ile iki temsil biçimi arasında bir bağlantı (tam olarak eşdeğer değilse) olduğunu kabul ediyorum: bu grafik yapıların doğal olarak dize diyagramları olarak yorumlanabileceği duygusu var , kompakt bir kapalı iki kategoride refleksif bir nesnenin endomorfizmlerini temsil eder ( burada bunun kısa bir açıklamasını yaptım ).α


10

Değişken-bağlayıcı-işlevinizin nasıl temsil edileceğinden ve hangi amaçla kullanmak istediğinizden emin değilim. Geri işaretçiler kullanıyorsanız, Andrej'in belirttiği gibi, ikame işleminin hesaplama karmaşıklığı, klasik alfa yeniden adlandırmadan daha iyi değildir.

Andrej'in cevabı hakkındaki yorumunuzdan bir dereceye kadar paylaşmakla ilgilendiğinizi düşünüyorum. Burada bazı girdiler sağlayabilirim.

Tipik bir tip lambda hesabında, diğer kuralların aksine zayıflama ve daralma sözdizimi yoktur.

Γt:TΓ,x:At:TW
Γ,x1:A,x2:At:TΓ,x:At:TC

Biraz sözdizimi ekleyelim:

Γt:TΓ,x:AWx(t):TW
Γ,x1:A,x2:At:TΓ,x:ACxx1,x2(t):TC

Cab,c() 'yukarı' değişkeni ve bağlayıcı değişkenler . Bu fikri Ian Mackie'nin " Kapalı Azaltma Etkileşimi Ağı Uygulamasından " birinden öğrendim .ab,c

Bu sözdizimiyle, her değişken bir kez bağlı olduğu ve bir kez kullanıldığı yerde tam olarak iki kez kullanılır. Bu, kendimizi belirli bir sözdiziminden uzaklaştırmamıza ve terimi, değişkenlerin ve terimlerin kenar olduğu bir grafik olarak görmemize izin verir.

Algoritmik karmaşıklıktan, artık değişkenlerden bağlayıcıya değil , bağlayıcıdan değişkene işaretçiler kullanabiliriz ve sabit bir zamanda ikameler alabiliriz.

Dahası, bu reform silmeyi, kopyalamayı ve paylaşmayı daha sadakatle izlememizi sağlar. Alt terimleri paylaşırken bir terimi kademeli olarak kopyalayan (veya silen) kurallar yazılabilir. Bunu yapmanın birçok yolu var. Bazı kısıtlı ortamlarda kazançlar oldukça şaşırtıcı .

Bu, etkileşim ağları, etkileşim birleştiriciler, açık ikame, doğrusal mantık, Lamping'in optimal değerlendirmesi, grafik paylaşma, ışık mantıkları ve diğer konulara yaklaşıyor.

Tüm bu konular benim için çok heyecan verici ve daha spesifik referanslar vermekten memnuniyet duyuyorum, ancak bunların herhangi birinin sizin için yararlı olup olmadığından ve ilgi alanlarınız olduğundan emin değilim.


6

Veri yapınız işe yarıyor ancak diğer yaklaşımlardan daha etkili olmayacak çünkü her beta indirgemesinde her argümanı kopyalamanız gerekiyor ve bağlı değişkenin oluşumları kadar çok kopya oluşturmanız gerekiyor. Bu şekilde alt aralar arasındaki bellek paylaşımını yok etmeye devam edersiniz. İşaretçi manipülasyonları içeren ve bu nedenle çok hataya açık olan saf olmayan bir çözüm önermiş olmanızla birlikte, muhtemelen zahmete değmez.

Ama bir deney gördüğüm için çok mutlu olurum! Bunu lambdaveri yapınızla alıp uygulayabilirsiniz (OCaml'ın işaretçileri vardır, bunlara referans denir ). Az ya da çok, sadece değiştirmek zorunda syntax.mlve norm.mlsizin sürümleri ile. Bu 150 satırdan az kod.


Teşekkürler! Gerçekte uygulamalar hakkında çok fazla düşünmediğimi ama esasen de Bruijn defter tutma veya alfa yeniden adlandırma ile uğraşmadan matematiksel kanıtlar yapabileceğimi itiraf ediyorum. Ancak, bir uygulamanın "gerekli olana kadar", yani kopyalar birbirinden ayrılıncaya kadar kopya yapmayarak bazı bellek paylaşımlarını elinde tutma şansı var mı?
Mike Shulman

Elbette, yazarken kopyala bir şey yapabilirsiniz, insanların uzun zamandır bu hileleri yaptığı işletim sistemleri. Bunun, yerleşik çözümlerden daha iyi çalışacağına dair bazı kanıtlar sağlaması gerekir. Bir çok kullanım şekline bağlı olacaktır. Örneğin, -redeces ile ilgili argümanların çoğu kopyalanıyor mu yoksa çoğunlukla doğrusal bir şekilde mi kullanılıyor? Tipik bir redex'te , hangisi genellikle daha büyük, veya ? Bu arada, açık alt bölümler de tembel olarak bir şeyler yapmanın bir yoludur. β(λx.e1)e2e1e2
Andrej Bauer

2
Matematiksel kanıtlarla ilgili olarak, şimdi tip-teorik sözdiziminin resmileştirilmesinden geçtim, deneyimim, kurulumu genelleştirdiğimizde ve daha soyut hale getirdiğimizde değil, daha somut hale getirdiğimizde avantajların elde edildiğidir . Örneğin, sözdizimini "bağlayıcı herhangi bir iyi tedavi yöntemi" ile parametrelendirebiliriz. Bunu yaptığımızda, hata yapmak daha zordur. Ayrıca, de Bruijn endeksleri ile tip teorisini resmileştirdim. Çok korkunç değil, özellikle etrafında saçma sapan şeyler yapmanızı engelleyen bağımlı türleriniz varsa.
Andrej Bauer

2
Eklemek için, temelde bu tekniği kullanan bir uygulama üzerinde çalıştım (ancak benzersiz tamsayılar ve haritalar, işaretçiler değil) ve gerçekten tavsiye etmem. Biz kesinlikle nerede şeyler düzgün klonlama cevapsız hata vardı (küçük bir kısmı mümkün olduğunca önlemek için çalışırken nedeniyle). Ama bence bazı GHC millet tarafından onu savundukları bir kağıt var (benzersiz isimleri üretmek için bir hash fonksiyonu kullandıklarına inanıyorum). Tam olarak ne yaptığınıza bağlı olabilir. Benim durumumda bu tür çıkarım / kontrol, ve orada oldukça zayıf uygun görünüyor.
Dan Doel

@MikeShulman Makul (Temel) karmaşıklık algoritmaları için (büyük ölçüde kopyalama ve silme), Lamping'in optimum azaltmasının 'soyut kısmı' gerekli olana kadar kopya yapmaz. Soyut kısım aynı zamanda, hesaplamaya hâkim olabilecek bazı ek açıklamalar gerektiren tam algoritmanın aksine tartışmalı olmayan kısımdır.
asukasz Lew

5

Diğer cevaplar çoğunlukla uygulama sorunlarını tartışıyor. Ana motivasyonunuzu çok fazla defter tutma olmadan matematiksel kanıtlar yapmak olarak belirttiğiniz için, işte bununla ilgili ana sorun var.

“Her değişkeni kapsamı içinde olan bir bağlayıcıyla eşleştiren bir işlev” dediğinizde: bu işlevin çıktı türü, ses verenden biraz daha incedir! Özellikle, fonksiyon “dikkate alınan terimin bağlayıcıları” gibi bir şeyde değer almalıdır - yani, terime bağlı olarak değişen bazı kümeler (ve açıkça herhangi bir yararlı şekilde daha büyük bir ortam kümesinin bir alt kümesi değildir). Bu nedenle, ikame olarak, sadece “bağlayıcı fonksiyonların birleşimini alamazsınız”: orijinal terimlerdeki bağlayıcılardan ikame sonucunda bağlayıcılara kadar bazı haritalara göre değerlerini yeniden endekslemeniz gerekir.

Bu reindexings, ya halının altına makul bir şekilde süpürülebilmeleri ya da bir tür işlevsellik ya da doğallık açısından güzelce paketlenebilmeleri açısından mutlaka “rutin” olmalıdır. Fakat aynı şey, adlandırılmış değişkenlerle çalışmaya ilişkin defter tutma için de geçerlidir. Genel olarak, bu yaklaşımla daha standart yaklaşımlarda olduğu gibi en azından defter tutmanın mümkün olacağı bana geliyor .

Bu bir yana, kavramsal olarak çok çekici bir yaklaşım ve dikkatlice çalıştığını görmek isterim - sözdiziminin bazı yönlerine standart yaklaşımlardan farklı bir ışık atabileceğini hayal edebiliyorum.


her değişkenin kapsamını takip etmek gerçekten de defter tutma gerektirir, ancak kişinin her zaman iyi kapsamlı sözdizimi ile sınırlandırılması gerektiği sonucuna atlamayın! İkame ve beta indirgeme gibi işlemler kötü kapsamlı terimlerle bile tanımlanabilir ve şüphe ediyorum ki eğer biri bu yaklaşımı resmileştirmek istiyorsa (yine, gerçekten kanıt ağlarına / "λ-grafiklere" yaklaşımı) Kanıt asistanı, önce daha genel operasyonları uygular ve daha sonra iyi kapsamda olma özelliğini koruduklarını kanıtlardı.
Noam Zeilberger

(Denemeye değer olduğunu kabul ettim ... birisinin zaten prova ağlarını / λ-grafiklerini resmileştirme bağlamında olması durumunda şaşırmamam rağmen.)
Noam Zeilberger


5

İşte yaklaşımınızı kullanarak -calculus'u kodlama girişimim (OCaml'de, yorumlarda birkaç açıklama ile). Terimleri dairesel değerler olarak tanımlamak mümkündür, bu da bu temsilin Coq'da iyi çalışma şansı olduğu anlamına gelir. Kapakların gösterilmesinde koindüktif bir tip gerektireceğini unutmayın ( aşağıda kullandığım hesabı dikkate almak için ).λLazy.t

Genel olarak, bunun harika bir temsil olduğunu düşünüyorum, ancak bağlayıcı bağlantıları kırmamak için işaretçilerle bazı defter tutma içerir. Sanırım değişebilir alanları kullanmak için kodu değiştirmek mümkün olurdu, ancak daha sonra Coq kodlama daha az doğrudan olacaktır. İşaretçi yapısı açık olmasına rağmen, bunun HOAS'a çok benzediğine hala inanıyorum. Bununla birlikte, Lazy.tbazı kodların yanlış zamanda değerlendirilmesinin mümkün olduğu anlamına gelir. Kodumda durum böyle değil çünkü sadece bir değişkenin bir değişkenle değiştirilmesi zaman içinde olabilir force(örneğin değerlendirme değil).

(* Representation of a term of the λ-calculus. *)
type term =
  | FVar of string      (* Free variable  *)
  | BVar of bvar        (* Bound variable *)
  | Appl of term * term (* Application    *)
  | Abst of abst        (* Abstraction    *)

(* A bound variable is a pointer to the corresponding binder. *)
and bvar = abst

(* A binder is represented as its body in which the bound variable points to
   the binder itself. Note that we need to use a thunk to be able to work
   underneath a binder (for substitution, evaluation, ...). A name can be
   given for easy printing, but no renaming is done. Only “visual capture”
   can happen since pointers are established the right way, even if names
   can clash. *)
and abst = { body : term Lazy.t ; name : string }

(* Terms can be built with recursive values for abstractions. *)

(* Krivine's notation is used for application (function in parentheses). *)

let id    : term = (* λx.x        *)
  Abst(let rec id = {body = lazy (BVar(id)); name = "x"} in id)

let idid  : term = (* (λx.x) λx.x *)
  Appl(id, id)

let delta : term = (* λx.(x) x *)
  Abst(let rec d = {body = lazy (Appl(BVar(d), BVar(d))); name = "x" } in d)

let weird : term = (* (λx.x) λy.(λx.(x) x) (C) y *)
  Appl(id, Abst(let rec x = {body = lazy (Appl(delta, Appl(FVar("C"),
    BVar(x)))); name = "y"} in x))

let omega : term = (* (λx.(x) x) λx.(x) x *)
  Appl(delta, delta)

(* Printing function is immediate. *)
let rec print : out_channel -> term -> unit = fun oc t ->
  match t with
  | FVar(x)   -> output_string oc x
  | BVar(x)   -> output_string oc x.name
  | Appl(t,u) -> Printf.fprintf oc "(%a) %a" print t print u
  | Abst(f)   -> Printf.fprintf oc "λ%s.%a" f.name print (Lazy.force f.body)

(* Substitution of variable [x] by [v] in the term [t]. Occurences of [x] in
   [t] are identified using physical equality ([BVar] case). The subtle case
   is [Abst], because we need to reestablish the physical link between the
   binder and the variable it binds. *)
let rec subst_var : bvar -> term -> term -> term = fun x t v ->
  match t with
  | FVar(_)   -> t
  | BVar(y)   -> if y == x then v else t
  | Appl(t,u) -> Appl(subst_var x t v, subst_var x u v)
  | Abst(f)   ->
      (* First compute the new body. *)
      let fv = subst_var x (Lazy.force f.body) v in
      (* Reestablish the physical link, using [subst_var] itself again. This
         requires a second traversal of the term. We could probably do both
         at once, but who cares the complexity is linear in [t] anyway. *)
      Abst(let rec g = {f with body = lazy (subst_var f fv (BVar(g)))} in g)

(* Actual substitution function. *)
let subst : abst -> term -> term = fun f v ->
  subst_var f (Lazy.force f.body) v

(* Normalization function (all the way, even under binders). *)
let rec eval : term -> term = fun t ->
  match t with
  | Appl(t,u) ->
      begin
        let v = eval u in
        match eval t with
        | Abst(f) -> eval (subst f v)
        | t       -> Appl(t,v)
      end
  | Abst(f)   ->
      (* Actual computation in the body. *)
      let fv = eval (Lazy.force f.body) in
      (* Here, the physical link is reestablished, but it is important to note
         that the computation of evaluation is done above. So the part below
         only takes a linear time in the size of the normal form of the body
         of the abstraction. *)
      Abst(let rec g = {f with body = lazy (subst_var f fv (BVar(g)))} in g)
  | _         ->
      t

let _ = Printf.printf "id         = %a\n%!" print id
let _ = Printf.printf "eval id    = %a\n%!" print (eval id)

let _ = Printf.printf "idid       = %a\n%!" print idid
let _ = Printf.printf "eval idid  = %a\n%!" print (eval idid)

let _ = Printf.printf "delta      = %a\n%!" print delta
let _ = Printf.printf "eval delta = %a\n%!" print (eval delta)

let _ = Printf.printf "omega      = %a\n%!" print omega
(* The following obviously loops. *)
(*let _ = Printf.printf "eval omega = %a\n%!" print (eval omega)*)

let _ = Printf.printf "weird      = %a\n%!" print weird
let _ = Printf.printf "eval weird = %a\n%!" print (eval weird)

(* Output produced:
id         = λx.x
eval id    = λx.x
idid       = (λx.x) λx.x
eval idid  = λx.x
delta      = λx.(x) x
eval delta = λx.(x) x
omega      = (λx.(x) x) λx.(x) x
weird      = (λx.x) λy.(λx.(x) x) (C) y
eval weird = λy.((C) y) (C) y
*)
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.