Y-birleştiricisi nedir? [kapalı]


392

Y-birleştirici, şeylerin “işlevsel” tarafındaki bir bilgisayar bilimi konseptidir. Çoğu programcı, hatta onları duymuşlarsa, birleştiriciler hakkında çok fazla şey bilmiyor.

  • Y-birleştiricisi nedir?
  • Birleştiriciler nasıl çalışır?
  • Neye iyi gelirler?
  • Prosedürel dillerde faydalılar mı?

12
Biraz ipucu, eğer benim gibi fonksiyonel dilleri öğreniyorsanız, rahat edene kadar birleştiricileri terk
etseniz

3
Bu sorunun editörün gravatarında gülümse :) Mads Torgensen'in blogunda ilgili link
Benjol


1
Y Combinator ile ilgili anlayışımı paylaşan kısa bir yazdım: gist.github.com/houtianze/b274e4b975a28fe08aee681699c3f7d0 "Y Combinator'ın özyinelemeli işlevi nasıl gerçekleştirdiğini"
anlattım

1
Bu soru nasıl "çok geniş"?
Rei Miyasaka

Yanıtlar:


201

Uzun bir okumaya hazırsanız, Mike Vanier'in harika bir açıklaması var . Uzun lafın kısası, özyinelemeyi doğal olarak desteklemeyen bir dilde uygulamanızı sağlar.


14
Yine de bir bağlantıdan biraz daha fazla; çok kısa bir özeti olan bir bağlantıdır . Daha uzun bir özet takdir edilecektir.
Martijn Pieters

2
Bu sadece bir bağlantı AMA bundan daha iyi olamaz. Bu cevap, çıkmak için hiçbir temel durum koşulu olmaksızın hak ediyor (1 oy); yani sonsuz özyineleme.
Yavar

7
@Andre MacFie: Çaba hakkında yorum yapmadım, kaliteye yorum yaptım. Genel olarak, Yığın Taşması ile ilgili politika, cevapların daha fazla bilgi bağlantısıyla birlikte müstakil olması gerektiğidir.
Jørgen Fogh

1
@galdre haklı. Harika bir link, ama sadece bir link. Ayrıca, aşağıdaki diğer 3 cevapta da belirtilmiştir, ancak hepsi kendi başına iyi açıklamalar olduğu için destekleyici bir belge olarak. Bu cevap OP'nin sorularını cevaplamaya bile çalışmaz.
toraritte

290

Y-birleştiricisi, işlevin kendi içinden başvuramadığınızda özyineleme sağlayan bir "işlevsel" (diğer işlevlerde çalışan bir işlev) 'dir. Bilgisayar bilimi teorisinde, özyinelemeyi genelleştirir , uygulamasını soyutlar ve böylece söz konusu işlevin fiili çalışmasından ayırır. Özyinelemeli işlev için bir derleme zamanı adı kullanmamanın yararı bir çeşit bonustur. =)

Bu lambda fonksiyonlarını destekleyen dillerde uygulanabilir . ifade lambdas ait tabanlı doğa genellikle adıyla kendilerini bakın anlamına gelir. Ve değişkeni bildirerek, ona atıfta bulunarak, sonra kendi kendine referans döngüsünü tamamlamak için lambda'yı atayarak bunun etrafında çalışmak kırılgandır. Lambda değişkeni kopyalanabilir ve öz referansı bozan orijinal değişken yeniden atanabilir.

Y-birleştiriciler statik yazım dillerinde ( yordamsal dillerin sıklıkla olduğu) uygulamak ve sıklıkla kullanmak zordur , çünkü genellikle yazım kısıtlamaları derleme zamanında söz konusu işlevin bilinmesi için argüman sayısını gerektirir. Bu, kullanılması gereken herhangi bir bağımsız değişken sayısı için bir y-birleştiricisinin yazılması gerektiği anlamına gelir.

Aşağıda, C # 'da bir Y-Kombinatörünün nasıl kullanılacağı ve çalıştığı hakkında bir örnek bulunmaktadır.

Bir Y-birleştiricisinin kullanılması, özyinelemeli bir işlev oluşturmanın "olağandışı" bir yolunu içerir. İlk olarak, işlevinizi kendisi yerine önceden var olan bir işlevi çağıran bir kod parçası olarak yazmalısınız:

// Factorial, if func does the same thing as this bit of code...
x == 0 ? 1: x * func(x - 1);

Daha sonra bunu, çağırmak için bir işlev alan bir işleve dönüştürür ve bunu yapan bir işlev döndürür. Buna işlevsel denir, çünkü bir işlevi alır ve onunla başka bir işleve neden olan bir işlem gerçekleştirir.

// A function that creates a factorial, but only if you pass in
// a function that does what the inner function is doing.
Func<Func<Double, Double>, Func<Double, Double>> fact =
  (recurs) =>
    (x) =>
      x == 0 ? 1 : x * recurs(x - 1);

Şimdi bir işlevi alan ve bir faktöriyel gibi görünen başka bir işlevi döndüren bir işleviniz var, ancak kendisini çağırmak yerine dış işleve geçirilen argümanı çağırıyor. Bunu faktöriyel olarak nasıl yaparsın? İç işlevi kendisine iletin. Y-Kombinatörü, özyinelemeyi tanıtabilecek kalıcı bir ada sahip bir işlev olarak yapar.

// One-argument Y-Combinator.
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> F)
{
  return
    t =>  // A function that...
      F(  // Calls the factorial creator, passing in...
        Y(F)  // The result of this same Y-combinator function call...
              // (Here is where the recursion is introduced.)
        )
      (t); // And passes the argument into the work function.
}

Faktöriyel çağrının kendisinden ziyade, faktöriyel faktöriyel jeneratörü çağırır (Y-Combinator'a özyinelemeli çağrı ile döndürülür). Ve t 'nin mevcut değerine bağlı olarak, jeneratörden döndürülen fonksiyon ya jeneratörü t - 1 ile tekrar çağıracak ya da sadece 1 geri dönerek özyinelemeyi sonlandıracaktır.

Karmaşık ve şifreli, ancak her şey çalışma zamanında sallanıyor ve çalışmasının anahtarı "ertelenmiş yürütme" ve iki işlevi kapsayacak şekilde özyinelemenin parçalanması. İç F bir sonraki iterasyonda çağrılacak bir argüman olarak geçirilir , ancak gerekirse .


5
Neden oh 'Y' ve 'F' parametresi dediniz! Sadece tür argümanlarında kayboluyorlar!
Brian Henk

3
Haskell'de, soyutlama özyineleme ile: fix :: (a -> a) -> ave akutu da istediğiniz kadar argüman işlevi olabilir. Bu, statik yazmanın bunu gerçekten zorlaştırdığı anlamına gelir.
Peaker

12
Mike Vanier'in açıklamasına göre, Y için tanımınız aslında bir birleştirici değil , çünkü özyinelemeli. "Açık özyinelemeyi ortadan kaldırma (tembel sürüm)" altında, C # kodunuzun tembel şema eşdeğerine sahiptir, ancak 2. noktada açıklar: "Bir birleştirici değildir, çünkü tanımın gövdesindeki Y, ancak tanım tamamlandığında sınırlıdır ... "Bence Y-birleştiriciler hakkındaki en güzel şey, bir işlevin sabit noktasını değerlendirerek özyineleme üretmeleridir. Bu şekilde, açık bir özyinelemeye ihtiyaçları yoktur.
GrantJ

İyi bir noktaya değindin. Bu cevabı gönderdiğimden birkaç yıl geçti. Vanier'in gönderisini şimdi gözden geçiriyorum, Y yazdığımı görüyorum, ancak Y-Kombinatörü değil. Kısa süre sonra gönderisini tekrar okuyacağım ve bir düzeltme gönderip gönderemeyeceğimi göreceğim. Bağırsaklarım, C # 'ın katı statik yazmasının sonunda bunu engelleyebileceği konusunda beni uyarıyor, ama ne yapabileceğimi göreceğim.
Chris Ammerman

1
@WayneBurkett Matematikte oldukça yaygın bir uygulamadır.
YoTengoUnLCD

102

Bunu , birkaç yıl önce yazdığım bir açıklama olan http://www.mail-archive.com/boston-pm@mail.pm.org/msg02716.html adresinden kaldırdım .

Bu örnekte JavaScript kullanacağım, ancak diğer birçok dil de çalışacak.

Amacımız, sadece 1 değişkenin fonksiyonlarını kullanarak ve atama olmadan, işleri isme göre tanımlayarak, vb. Kullanarak 1 değişkenli bir özyinelemeli fonksiyon yazabilmektir (neden bu bizim amacımız başka bir sorudur, bunu sadece verildi imkansız gibi görünüyor, ha? Örnek olarak, faktöriyeli uygulayalım.

Adım 1, biraz kandırdığımızda bunu kolayca yapabileceğimizi söylemek. 2 değişkenli ve atama işlevlerini kullanarak, en azından özyinelemeyi ayarlamak için atama kullanmak zorunda kalabilirsiniz.

// Here's the function that we want to recurse.
X = function (recurse, n) {
  if (0 == n)
    return 1;
  else
    return n * recurse(recurse, n - 1);
};

// This will get X to recurse.
Y = function (builder, n) {
  return builder(builder, n);
};

// Here it is in action.
Y(
  X,
  5
);

Şimdi daha az hile yapıp yapamayacağımızı görelim. Öncelikle ödevi kullanıyoruz, ama buna gerek yok. X ve Y'yi satır içine yazabiliriz.

// No assignment this time.
function (builder, n) {
  return builder(builder, n);
}(
  function (recurse, n) {
    if (0 == n)
      return 1;
    else
      return n * recurse(recurse, n - 1);
  },
  5
);

Ancak 1 değişkenli bir işlev elde etmek için 2 değişkenli işlevleri kullanıyoruz. Bunu düzeltebilir miyiz? Haskell Curry adında akıllı bir adam temiz bir hile var, eğer daha iyi sipariş fonksiyonlarına sahipseniz, sadece 1 değişkenli fonksiyonlara ihtiyacınız var. Kanıt, 2 (veya genel durumda daha fazla) değişkenin işlevlerinden, aşağıdaki gibi tamamen mekanik bir metin dönüşümü ile 1 değişkene ulaşabileceğinizdir:

// Original
F = function (i, j) {
  ...
};
F(i,j);

// Transformed
F = function (i) { return function (j) {
  ...
}};
F(i)(j);

burada ... tamamen aynı kalıyor. (Bu hile mucidinden sonra "körelme" olarak adlandırılır. Haskell dili, Haskell Curry için de adlandırılmıştır. Bu işe yaramaz trivia altında dosya.) Şimdi bu dönüşümü her yere uygulayın ve son versiyonumuzu aldık.

// The dreaded Y-combinator in action!
function (builder) { return function (n) {
  return builder(builder)(n);
}}(
  function (recurse) { return function (n) {
    if (0 == n)
      return 1;
    else
      return n * recurse(recurse)(n - 1);
  }})(
  5
);

Denemek için çekinmeyin. uyarı () döndürür, bir düğmeye bağlayın, ne olursa olsun. Bu kod, 2 değişkenli atama, bildirim veya işlevler kullanmadan faktöriyelleri yinelemeli olarak hesaplar. (Ama nasıl çalıştığını izlemeye çalışmak muhtemelen başınızı döndürecektir. Ve türetme olmadan teslim etmek, biraz yeniden biçimlendirildiğinde şaşkına dönecek ve karıştırılacak olan kodla sonuçlanacaktır.)

Faktöriyeli olarak özyinelemeli tanımlayan 4 satırı istediğiniz diğer özyinelemeli işlevlerle değiştirebilirsiniz.


Güzel açıklama. Neden yazdın function (n) { return builder(builder)(n);}yerine builder(builder)?
v7d8dpo4

@ v7d8dpo4 Çünkü 2 değişkenli bir işlevi currying kullanarak bir değişkenin üst düzey işlevine dönüştürüyordum.
btilly

Kapatmaya ihtiyacımızın nedeni bu mu?
TheChetan

1
@TheChetan Kapanışları, anonim bir işleve yapılan çağrıların arkasındaki özelleştirilmiş davranışı bağlayabilmemizi sağlar. Bu sadece başka bir soyutlama tekniğidir.
btilly

85

Bunu baştan aşağı inşa etmenin herhangi bir faydası olup olmadığını merak ediyorum. Bakalım. Temel, özyinelemeli bir faktör işlevi:

function factorial(n) {
    return n == 0 ? 1 : n * factorial(n - 1);
}

Yeniden facthesaplayalım ve hesaplamanın kendisini yapmak yerine anonim bir faktöriyel hesaplama işlevi döndüren adlı yeni bir işlev oluşturalım :

function fact() {
    return function(n) {
        return n == 0 ? 1 : n * fact()(n - 1);
    };
}

var factorial = fact();

Bu biraz garip, ama yanlış bir şey yok. Her adımda yeni bir faktöriyel fonksiyon üretiyoruz.

Bu aşamadaki özyineleme hala oldukça açıktır. factFonksiyon kendi adının farkında olması gerekir. Yinelemeli aramayı parametrelendirelim:

function fact(recurse) {
    return function(n) {
        return n == 0 ? 1 : n * recurse(n - 1);
    };
}

function recurser(x) {
    return fact(recurser)(x);
}

var factorial = fact(recurser);

Bu harika, ama recurseryine de kendi adını bilmeli. Bunu da parametreleştirelim:

function recurser(f) {
    return fact(function(x) {
        return f(f)(x);
    });
}

var factorial = recurser(recurser);

Şimdi, recurser(recurser)doğrudan aramak yerine, sonucunu döndüren bir sarmalayıcı işlevi oluşturalım:

function Y() {
    return (function(f) {
        return f(f);
    })(recurser);
}

var factorial = Y();

Artık recurserisminden tamamen kurtulabiliriz ; bu sadece Y'nin iç işlevine yönelik bir argümandır, bu işlevle değiştirilebilir:

function Y() {
    return (function(f) {
        return f(f);
    })(function(f) {
        return fact(function(x) {
            return f(f)(x);
        });
    });
}

var factorial = Y();

Hala atıfta bulunulan tek harici isim fact, ancak şimdi, tam, genel, çözümü yaratan kolayca parametrelendirildiği açıkça anlaşılmalıdır:

function Y(le) {
    return (function(f) {
        return f(f);
    })(function(f) {
        return le(function(x) {
            return f(f)(x);
        });
    });
}

var factorial = Y(function(recurse) {
    return function(n) {
        return n == 0 ? 1 : n * recurse(n - 1);
    };
});

JavaScript Benzer bir açıklama: igstan.ro/posts/...
Pops

1
İşlevi tanıtırken beni kaybettin recurser. Ne yaptığı veya neden olduğu hakkında en ufak bir fikir değil.
Mörre

2
Açıkça özyinelemeyen işlevler için genel bir özyinelemeli çözüm oluşturmaya çalışıyoruz. recurserBize bir özyinelemeli versiyonunu verir çünkü fonksiyonu, bu hedefe doğru ilk adımdır factbu isimde kendisini başvuran olmadı.
Wayne

@WayneBurkett Can Böyle Y bağdaştırıcının yeniden yazın: function Y(recurse) { return recurse(recurse); } let factorial = Y(creator => value => { return value == 0 ? 1 : value * creator(creator)(value - 1); });. Ve ben bu şekilde sindiriyorum (doğru olup olmadığından emin değilim): İşleve açıkça (bir birleştirici olarak izin verilmez) atıfta bulunmadan , iki kısmen uygulanmış / curried işlevi (bir yaratıcı işlevi ve hesaplama işlevi) kullanabiliriz. hangi hesaplama fonksiyonu için bir isim gerek kalmadan özyinelemeli elde lambda / anonim fonksiyonlar oluşturabilirsiniz?
neevek

50

Yukarıdaki cevapların çoğu Y-birleştiricisinin olduğunu ancak ne olduğunu için .

Sabit nokta combinators olduğunu göstermek için kullanılır , lambda taşı olan tam turing . Bu, hesaplama teorisinde çok önemli bir sonuçtur ve fonksiyonel programlama için teorik bir temel sağlar .

Sabit nokta birleştiricilerinin incelenmesi de işlevsel programlamayı gerçekten anlamama yardımcı oldu. Gerçi gerçek programlamada onlar için hiç bir şey bulamadım.


24

JavaScript'te y-birleştirici :

var Y = function(f) {
  return (function(g) {
    return g(g);
  })(function(h) {
    return function() {
      return f(h(h)).apply(null, arguments);
    };
  });
};

var factorial = Y(function(recurse) {
  return function(x) {
    return x == 0 ? 1 : x * recurse(x-1);
  };
});

factorial(5)  // -> 120

Düzenle : Ben kod bakarak çok şey öğrenmek, ama bu biraz arka plan olmadan yutmak için biraz zor - üzgünüm. Diğer cevaplar tarafından sunulan bazı genel bilgilerle, neler olduğunu ayırmaya başlayabilirsiniz.

Y işlevi "y-birleştiricisi" dir. Şimdi var factorialY'nin kullanıldığı satıra bir göz atın . recurseDaha sonra iç işlevde de kullanılan bir parametreye (bu örnekte ) sahip bir işlev ilettiğinize dikkat edin . Parametre adı temel olarak, özyinelemeli bir çağrı yapmasına izin veren iç fonksiyonun adı olur ( recurse()tanımında kullandığı için.) Y-birleştirici, aksi halde anonim iç fonksiyonu, iletilen fonksiyonun parametre adıyla ilişkilendirme sihrini yerine getirir. Y.

Y'nin sihri nasıl yaptığını tam olarak açıklamak için bağlantılı makaleye göz atın (btw tarafından değil).


6
Eğer arguments.callee ile şimdiki işlevi erişebilir çünkü JavaScript anonim özyinelemeye yapmak için bir Y-bağdaştırıcının ihtiyacı yoktur (bkz en.wikipedia.org/wiki/... )
xitrium

6
arguments.calleeKatı Modda izin verilmiyor: developer.mozilla.org/tr/JavaScript/…
dave1010

2
Yine de herhangi bir işleve bir ad verebilirsiniz ve işlev ifadesi varsa bu ad yalnızca işlevin içinde bilinir. (function fact(n){ return n <= 1? 1 : n * fact(n-1); })(5)
Esailija


18

İşlevsel programlamaya derinlemesine rastlamayan ve şimdi başlamak istemeyen, ancak biraz meraklı olan programcılar için:

Y birleştirici, işlevlerin adlara sahip olamayacağı, ancak değişken olarak döndürülebildiği, dönüş değerleri olarak kullanılabildiği ve diğer işlevlerde tanımlandığı bir durumda özyineleme uygulamanızı sağlayan bir formüldür.

İşlevi bir argüman olarak kendisine ileterek çalışır, böylece kendini çağırabilir.

Gerçekten matematik olan ancak etkili bir programlama dili olan ve bilgisayar bilimi ve özellikle fonksiyonel programlama için oldukça temel olan lambda hesabının bir parçasıdır.

Programlama dilleri işlevleri adlandırmanıza izin verdiği için Y birleştiricisinin günlük pratik değeri sınırlıdır.

Bir polis kadrosunda tanımlamanız gerekiyorsa, şöyle görünür:

Y = λf. (Λx.f (xx)) (λx.f (xx))

Genellikle tekrarlanan nedeniyle tespit edebilirsiniz (λx.f (x x)).

λSemboller Adından hesabı lambda verir Yunan harfi lambda, vardır ve bir sürü (λx.t)bu ne lambda hesabı görünüyor gibi çünkü stil açısından.


kabul edilen cevap bu olmalıdır. Btw ile U x = x x, Y = U . (. U)(işaretlerle Haskell gibi kötüye). IOW, uygun birleştiricilerle Y = BU(CBU),. Böylece Yf = U (f . U) = (f . U) (f . U) = f (U (f . U)) = f ((f . U) (f . U)),.
Ness Ness

13

Anonim özyineleme

Sabit noktalı bir birleştirici, fixtanım gereği denkliği karşılayan daha üst düzey bir işlevdir

forall f.  fix f  =  f (fix f)

fix fxsabit nokta denklemine bir çözümü temsil eder

               x  =  f x

Doğal bir sayının faktöriyeli,

fact 0 = 1
fact n = n * fact (n - 1)

fixGenel / μ-özyinelemeli işlevler üzerinde keyfi yapıcı kanıtlar kullanılarak , adsız öz-referans olmadan türetilebilir.

fact n = (fix fact') n

nerede

fact' rec n = if n == 0
                then 1
                else n * rec (n - 1)

öyle ki

   fact 3
=  (fix fact') 3
=  fact' (fix fact') 3
=  if 3 == 0 then 1 else 3 * (fix fact') (3 - 1)
=  3 * (fix fact') 2
=  3 * fact' (fix fact') 2
=  3 * if 2 == 0 then 1 else 2 * (fix fact') (2 - 1)
=  3 * 2 * (fix fact') 1
=  3 * 2 * fact' (fix fact') 1
=  3 * 2 * if 1 == 0 then 1 else 1 * (fix fact') (1 - 1)
=  3 * 2 * 1 * (fix fact') 0
=  3 * 2 * 1 * fact' (fix fact') 0
=  3 * 2 * 1 * if 0 == 0 then 1 else 0 * (fix fact') (0 - 1)
=  3 * 2 * 1 * 1
=  6

Bu resmi kanıt,

fact 3  =  6

yeniden yazma için yöntemsel olarak sabit nokta birleştirici eşdeğerini kullanır

fix fact'  ->  fact' (fix fact')

Lambda hesabı

Türlenmemiş lambda taşı formalizm bir serbest içerik oluşur

E ::= v        Variable
   |  λ v. E   Abstraction
   |  E E      Application

burada vbirlikte, değişkenler üzerinde değişmektedir , beta ve eta indirgeme kuralları

(λ x. B) E  ->  B[x := E]                                 Beta
  λ x. E x  ->  E          if x doesn’t occur free in E   Eta

Beta indirgeme x, soyutlamanın ("işlev") gövdesindeki değişkenin tüm serbest oluşumlarını Bifade ("argüman") ile değiştirir E. Eta azaltımı gereksiz soyutlamayı ortadan kaldırır. Bazen formalizmden çıkarılır. Bir indirgenemez azalma kuralı geçerli olduğu ifade, içinde , normal veya kanonik formu .

λ x y. E

için kestirme

λ x. λ y. E

(soyutlama çok yönlülüğü),

E F G

için kestirme

(E F) G

(başvuru sol ilişkilendirilebilirliği),

λ x. x

ve

λ y. y

olan alfa-eşdeğeri .

Soyutlama ve uygulama, lambda hesabının sadece iki “ilkel” i olmakla birlikte , keyfi olarak karmaşık verilerin ve işlemlerin kodlanmasına izin verir .

Kilise rakamları, Peano-aksiyomatik doğallara benzer doğal sayıların bir kodlamasıdır.

   0  =  λ f x. x                 No application
   1  =  λ f x. f x               One application
   2  =  λ f x. f (f x)           Twofold
   3  =  λ f x. f (f (f x))       Threefold
    . . .

SUCC  =  λ n f x. f (n f x)       Successor
 ADD  =  λ n m f x. n f (m f x)   Addition
MULT  =  λ n m f x. n (m f) x     Multiplication
    . . .

Resmi bir kanıt

1 + 2  =  3

beta indirgeme yeniden yazma kuralını kullanarak:

   ADD                      1            2
=  (λ n m f x. n f (m f x)) (λ g y. g y) (λ h z. h (h z))
=  (λ m f x. (λ g y. g y) f (m f x)) (λ h z. h (h z))
=  (λ m f x. (λ y. f y) (m f x)) (λ h z. h (h z))
=  (λ m f x. f (m f x)) (λ h z. h (h z))
=  λ f x. f ((λ h z. h (h z)) f x)
=  λ f x. f ((λ z. f (f z)) x)
=  λ f x. f (f (f x))                                       Normal form
=  3

combinators

Lambda hesabında, birleştiriciler serbest değişken içermeyen soyutlardır. En basit haliyle: Ikimlik birleştirici

λ x. x

kimlik fonksiyonuna izomorfik

id x = x

Bu tür birleştiriciler , SKI sistemi gibi birleştirici taşlarının ilkel operatörleridir .

S  =  λ x y z. x z (y z)
K  =  λ x y. x
I  =  λ x. x

Beta azaltımı güçlü bir şekilde normalleştirilmemektedir ; indirgenebilir ifadelerin tümü değil, "redexes" beta indirgeme altında normal forma dönüşür. Basit bir örnek, omega ωbirleştiricinin farklı uygulamasıdır

λ x. x x

kendisine:

   (λ x. x x) (λ y. y y)
=  (λ y. y y) (λ y. y y)
. . .
=  _|_                     Bottom

En soldaki alt ifadelerin ("kafalar") azaltılmasına öncelik verilir. Geçerli emir ikame edilmeden önce argümanları normalleştirir, normal emir vermez. İki strateji, istekli değerlendirmeye, örneğin C'ye ve tembel değerlendirmeye, örneğin Haskell'e benzer.

   K          (I a)        (ω ω)
=  (λ k l. k) ((λ i. i) a) ((λ x. x x) (λ y. y y))

istekli uygulama siparişi beta indirgeme altında ayrılıyor

=  (λ k l. k) a ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ y. y y) (λ y. y y))
. . .
=  _|_

katı anlambilimden beri

forall f.  f _|_  =  _|_

ancak tembel normal sıra beta azaltma altında yakınsar

=  (λ l. ((λ i. i) a)) ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ x. x x) (λ y. y y))
=  a

Bir ifadenin normal bir formu varsa, normal sıralı beta azaltma ifadeyi bulur.

Y

Sabit nokta birleştiricisinin temel özelliğiY

λ f. (λ x. f (x x)) (λ x. f (x x))

tarafından verildi

   Y g
=  (λ f. (λ x. f (x x)) (λ x. f (x x))) g
=  (λ x. g (x x)) (λ x. g (x x))           =  Y g
=  g ((λ x. g (x x)) (λ x. g (x x)))       =  g (Y g)
=  g (g ((λ x. g (x x)) (λ x. g (x x))))   =  g (g (Y g))
. . .                                      . . .

Denklik

Y g  =  g (Y g)

izomorfik

fix f  =  f (fix f)

Türlenmemiş lambda hesabı genel / μ-özyinelemeli işlevler üzerinde keyfi yapıcı kanıtları kodlayabilir.

 FACT  =  λ n. Y FACT' n
FACT'  =  λ rec n. if n == 0 then 1 else n * rec (n - 1)

   FACT 3
=  (λ n. Y FACT' n) 3
=  Y FACT' 3
=  FACT' (Y FACT') 3
=  if 3 == 0 then 1 else 3 * (Y FACT') (3 - 1)
=  3 * (Y FACT') (3 - 1)
=  3 * FACT' (Y FACT') 2
=  3 * if 2 == 0 then 1 else 2 * (Y FACT') (2 - 1)
=  3 * 2 * (Y FACT') 1
=  3 * 2 * FACT' (Y FACT') 1
=  3 * 2 * if 1 == 0 then 1 else 1 * (Y FACT') (1 - 1)
=  3 * 2 * 1 * (Y FACT') 0
=  3 * 2 * 1 * FACT' (Y FACT') 0
=  3 * 2 * 1 * if 0 == 0 then 1 else 0 * (Y FACT') (0 - 1)
=  3 * 2 * 1 * 1
=  6

(Çarpma gecikti, izdiham)

Kilisenin türetilmemiş lambda hesabı için, sabit nokta birleştiricilerinin tekrar tekrar numaralandırılabilir bir sonsuzluğu olduğu gösterilmiştir Y.

 X  =  λ f. (λ x. x x) (λ x. f (x x))
Y'  =  (λ x y. x y x) (λ y x. y (x y x))
 Z  =  λ f. (λ x. f (λ v. x x v)) (λ x. f (λ v. x x v))
 Θ  =  (λ x y. y (x x y)) (λ x y. y (x x y))
  . . .

Normal dereceli beta azaltma, genişletilmemiş türsüz lambda hesabını bir Turing-complete yeniden yazma sistemi yapar.

Haskell'de sabit nokta birleştirici zarif bir şekilde uygulanabilir

fix :: forall t. (t -> t) -> t
fix f = f (fix f)

Haskell'in tembellikleri, tüm alt ifadeler değerlendirilmeden önce ince bir hal alır.

primes :: Integral t => [t]
primes = sieve [2 ..]
   where
      sieve = fix (\ rec (p : ns) ->
                     p : rec [n | n <- ns
                                , n `rem` p /= 0])


4
Cevabın bütünlüğünü takdir etsem de, ilk satır aradan sonra resmi matematik geçmişi az olan bir programcıya hiçbir şekilde ulaşılamaz.
Jared Smith

4
@ jared-smith Bu sorunun cevabı, Y birleştiricinin arkasındaki CS / matematik kavramları hakkında tamamlayıcı bir Wonkaian hikayesi anlatmak içindir. Muhtemelen, tanıdık kavramlara mümkün olan en iyi benzerliklerin zaten diğer cevap verenler tarafından çizildiğini düşünüyorum. Şahsen, her zaman güzel bir benzetme üzerinde bir fikrin gerçek kökeni, radikal yeniliği ile karşı karşıya kalmayı sevdim . En geniş analojileri uygunsuz ve kafa karıştırıcı buluyorum.

1
Merhaba, kimlik birleştirici λ x . x, bugün nasılsın?
MaiaVictor

Bu cevabı en çok seviyorum . Tüm sorularımı sildi!
Öğrenci

11

Diğer cevaplar, önemli bir gerçek olmaksızın, buna oldukça kısa bir cevap veriyor: Sabit nokta birleştiriciyi bu kıvrımlı şekilde herhangi bir pratik dilde uygulamanıza gerek yok ve bunu yapmak pratik bir amaca hizmet etmiyor (hariç "bak, Y-birleştiricinin ne olduğunu biliyorum dır-dir"). Önemli bir teorik kavram ama pratik değeri az.


6

Y-Combinator ve Factorial fonksiyonunun bir JavaScript uygulamasıdır (Douglas Crockford'un şu adresteki makalesinde: http://javascript.crockford.com/little.html ).

function Y(le) {
    return (function (f) {
        return f(f);
    }(function (f) {
        return le(function (x) {
            return f(f)(x);
        });
    }));
}

var factorial = Y(function (fac) {
    return function (n) {
        return n <= 2 ? n : n * fac(n - 1);
    };
});

var number120 = factorial(5);

6

Bir Y-Kombine, akı kapasitörü için başka bir isimdir.


4
çok komik. :) genç (er) olanlar referansı tanımayabilir.
Ness

2
haha! Evet, genç olan (ben) hala anlayabiliyor ...


Bu cevabın özellikle İngilizce olmayan konuşmacılar için kafa karıştırıcı olabileceğini düşünüyorum. Bu mizahi popüler bir kültür referansı olduğunu anlamadan (ya da asla) bu iddiayı anlamak için biraz zaman ayırabiliriz. (Bunu beğendim, eğer buna cevap versem ve öğrenen birinin cesaretini kırdığını keşfetseydim kendimi kötü hissederim)
mike

5

Kendimi onunla başa çıkmaya yardımcı olmak için hem Clojure'da hem de Şemada Y-Combinator'a bir çeşit "aptal kılavuzu" yazdım. "Küçük Schemer" deki materyallerden etkileniyorlar

Şemada: https://gist.github.com/z5h/238891

veya Clojure: https://gist.github.com/z5h/5102747

Her iki öğretici de yorumlarla birlikte serpiştirilmiştir ve favori düzenleyicinize kesilip yapıştırılmalıdır.


5

Kombinatörlere yeni başlayan biri olarak Mike Vanier'in makalesini buldum (teşekkürler Nicholas Mancuso) gerçekten yardımcı olduğunu . Bir özet yazmak istiyorum, anlayışımı belgelemenin yanı sıra, başkalarına yardımcı olabilirse çok memnun olurum.

Gönderen bok için daha az bok

Faktöriyel olarak örnek olarak, almost-factorialsayının faktöriyelini hesaplamak için aşağıdaki fonksiyonu kullanırız x:

def almost-factorial f x = if iszero x
                           then 1
                           else * x (f (- x 1))

Yukarıdaki sözde kodda, almost-factorialişlevi fve sayıyı alır x( almost-factorialcurried, bu nedenle işlevi almak fve 1-arity işlevini döndürmek olarak görülebilir ).

Tüm almost-factorialhesaplar için fabrika xiçin fabrika nedeniyle, delegeler hesaplama x - 1işlevi file bu sonuç birikir x(- x 1), bu durumda, bu sonucu (X çarpar).

Gibi görülebilir almost-factorialbir alır bok (sadece sayısı kadar hesaplayabilir faktöriyel fonksiyonunun versiyonu x - 1) ve bir döner az bok faktörlü versiyonunu (sayı kadar hesaplar x). Bu formda olduğu gibi:

almost-factorial crappy-f = less-crappy-f

Faktöriyenin daha az berbat versiyonunu tekrar tekrar geçersekalmost-factorial , sonunda istenen faktöriyel fonksiyonumuzu alırız f. Nerede olduğu düşünülebilir:

almost-factorial f = f

Düzeltme noktalı

Bunun almost-factorial f = fanlamı f, fonksiyonun sabit noktasıdıralmost-factorial .

Bu, yukarıdaki işlevlerin ilişkilerini görmenin gerçekten ilginç bir yoluydu ve benim için aha bir andı. (eğer yapmadıysanız lütfen Mike'ın düzeltme noktasında yazısını okuyun)

Üç işlev

Genelleştirmek için, özyinelemeli olmayan bir fonksiyonumuz var fn(neredeyse faktöriyelimiz gibi), düzeltme noktası fonksiyonuna fr(f gibi) sahibiz , o Yzaman verdiğinizde Y fn, Ydüzeltme noktası fonksiyonunu döndürür fn.

Yani özetle (üstlenerek basitleştirilmiş frtek bir parametre alır; xyozlaşmış için x - 1, x - 2... özyinelemede):

  • Temel hesaplamaları şu şekilde tanımlarız fn:, def fn fr x = ...accumulate x with result from (fr (- x 1))bu neredeyse kullanışlı bir işlevdir - fndoğrudan üzerinde kullanamasak da x, çok yakında faydalı olacaktır. Bu özyinelemesiz , sonucunu hesaplamak fniçin bir işlev kullanırfr
  • fn fr = fr, frBir düzeltme noktası olan fn, frbir kullanışlı funciton, kullanabileceğimiz früzerinde xbizim sonuç almak için
  • Y fn = fr, YBir fonksiyonun sabitleme noktasını döndürür Y bizim döner neredeyse kullanışlı fonksiyon fniçine kullanışlı fr

Türetme Y(dahil değil)

Ben türetmeyi atlayıp Yanlamaya başlayacağım Y. Mike Vainer'ın gönderisinde çok fazla ayrıntı var.

Şekli Y

Yşu şekilde tanımlanır ( lambda hesap biçiminde):

Y f = λs.(f (s s)) λs.(f (s s))

sİşlevlerin solundaki değişkeni değiştirirsek,

Y f = λs.(f (s s)) λs.(f (s s))
=> f (λs.(f (s s)) λs.(f (s s)))
=> f (Y f)

Gerçekten de, sonucu (Y f)sabitleme noktasıdır f.

Neden (Y f)çalışıyor?

İmzasını bağlı f, (Y f)basitleştirmek için, herhangi Arity bir fonksiyonu olabilir, en varsayalım (Y f)sadece bizim faktöriyel fonksiyonu gibi, bir parametre alır.

def fn fr x = accumulate x (fr (- x 1))

o zamandan beri fn fr = frdevam ediyoruz

=> accumulate x (fn fr (- x 1))
=> accumulate x (accumulate (- x 1) (fr (- x 2)))
=> accumulate x (accumulate (- x 1) (accumulate (- x 2) ... (fn fr 1)))

en iç yinelemeli hesaplama sonlandırıldığında (fn fr 1)temel durum ve fnkullanmayan frhesaplanmasında.

YTekrar bakmak :

fr = Y fn = λs.(fn (s s)) λs.(fn (s s))
=> fn (λs.(fn (s s)) λs.(fn (s s)))

Yani

fr x = Y fn x = fn (λs.(fn (s s)) λs.(fn (s s))) x

Bana göre, bu düzenin büyülü kısımları:

  • fnve frbirbirine bağımlı: içeride fr'sarar' fn, her frhesaplamak için kullanıldığında x, 'doğar' ('kaldırır'?) fnve hesaplamayı buna devreder fn(kendi içinde frve içinde x); Öte yandan, daha küçük bir sorunun sonucunu hesaplamak için fnbağlıdır frve kullanırfrx-1 .
  • Zamanda frtanımlamak için kullanılır fn(zaman fnkullanım frfaaliyetlerinde), gerçek frhenüz tanımlanmamıştır.
  • Bu var fngerçek iş mantığını tanımlayan. Göre fn, Yyaratır fr- özel bir biçimde yardımcı bir işlevi - hesaplanmasını kolaylaştırmak için fnbir de yinelemeli bir şekilde.

Şu Yan bu şekilde anlamama yardımcı oldu , umarım yardımcı olur.

BTW, Lambda Calculus ile Fonksiyonel Programlamaya Giriş kitabını çok iyi buldum, sadece bunun bir parçasıyım Yve kitapta kafamı bulamadığım gerçeği beni bu mesaja götürdü.


5

Aşağıda, Nicholas Mancuso'nun cevabında belirtilen makalede (okumaya değer TOTALY) derlenen orijinal soruların cevapları ve diğer cevaplar bulunmaktadır:

Y-birleştiricisi nedir?

Y birleştirici, tek işlevli, yinelemeli olmayan bir işlev alan ve işlevin özyinelemeli.


Biraz özyinelemeli =), ancak daha derinlemesine bir tanım:

Bir birleştirici - sadece serbest değişkenleri olmayan bir lambda ifadesidir.
Serbest değişken - bağlı değişken olmayan bir değişkendir.
Bağlı değişken - bağımsız değişkenlerinden biri olarak bu değişken adına sahip olan bir lambda ifadesinin gövdesinde bulunan değişken.

Bunu düşünmenin başka bir yolu, birleştiricinin böyle bir lambda ifadesidir, burada bir birleştiricinin adını bulunduğu her yerde tanımıyla değiştirebilir ve hala her şeye sahip olabilirsiniz (birleştirici eğer sonsuz döngüye girersiniz) lambda gövdesi içinde kendisine referans içerir).

Y-birleştirici sabit noktalı bir birleştiricidir.

Bir işlevin sabit noktası, işlev tarafından kendisine eşlenen işlev etki alanının bir öğesidir.
Yani, demek ki cfonksiyonunun sabit bir nokta olduğunu f(x)eğer f(c) = c
bu yollarlaf(f(...f(c)...)) = fn(c) = c

Birleştiriciler nasıl çalışır?

Aşağıdaki örnekler güçlü + dinamik yazmayı varsaymaktadır :

Tembel (normal sıralı) Y-birleştirici:
Bu tanım tembel (ayrıca: ertelenmiş, ihtiyaca göre çağrılabilir) değerlendirme - bir ifadenin değerini gerekinceye kadar değerlendirilmesini geciktiren değerlendirme stratejisi için de geçerlidir.

Y = λf.(λx.f(x x)) (λx.f(x x)) = λf.(λx.(x x)) (λx.f(x x))

Bunun anlamı, belirli bir işlev için f(özyinelemeli olmayan bir işlevdir), karşılık gelen özyinelemeli işlevin önce hesaplanarak λx.f(x x)ve daha sonra bu lambda ifadesinin kendisine uygulanmasıyla elde edilebilmesidir .

Katı (uygulanabilir-sipariş) Y-birleştirici:
Bu tanım, bir ifadenin bir değişkene bağlanır bağlanmaz değerlendirildiği katı (ayrıca: istekli, açgözlü) değerlendirme - değerlendirme stratejisine sahip diller için geçerlidir.

Y = λf.(λx.f(λy.((x x) y))) (λx.f(λy.((x x) y))) = λf.(λx.(x x)) (λx.f(λy.((x x) y)))

Doğasında tembel olanla aynıdır λ, lambda'nın vücut değerlendirmesini geciktirmek için sadece ekstra bir sargıya sahiptir . Bu konuyla ilgili bir soru daha sordum .

Neye iyi gelirler?

Çalınan Chris Ammerman'ın cevabından ödünç alınan : Y-birleştirici, özyinelemeyi genelleştirir, uygulamasını soyutlar ve böylece söz konusu işlevin fiili çalışmasından ayırır.

Y-birleştiricinin bazı pratik uygulamaları olsa da, temel olarak teorik bir kavramdır, anlayışı genel vizyonunuzu genişletecek ve muhtemelen analitik ve geliştirici becerilerinizi artıracaktır.

Prosedürel dillerde faydalılar mı?

Mike Vanier tarafından belirtildiği gibi : statik olarak yazılan birçok dilde bir Y birleştiriciyi tanımlamak mümkündür, ancak (en azından gördüğüm örneklerde) bu tür tanımlar genellikle belli olmayan bir tür hackery gerektirir, çünkü Y birleştiricinin kendisi t Basit bir statik tipe sahip. Bu, bu makalenin kapsamı dışındadır, bu yüzden daha fazla bahsetmeyeceğim

Ve Chris Ammerman'ın belirttiği gibi : prosedürel dillerin çoğunun statik yazımı vardır.

Yani buna cevap verin - tam olarak değil.


4

Y-birleştirici anonim özyineleme uygular. Yani yerine

function fib( n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }

yapabilirsin

function ( fib, n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }

elbette, y-birleştirici yalnızca ad-çağrı dillerinde çalışır. Bunu herhangi bir normal çağrı-değer dilinde kullanmak istiyorsanız, ilgili z-birleştiriciye ihtiyacınız olacaktır (y-birleştirici ayrılır / sonsuz döngü).


Y birleştirici, değer bazında ve tembel değerlendirme ile çalışabilir.
Quelklef

3

Bir sabit nokta birleştirici (veya sabit nokta işleci), diğer işlevlerin sabit bir noktasını hesaplayan üst düzey bir işlevdir. Bu işlem programlama dili teorisiyle ilgilidir, çünkü dilin çalışma zamanı motorundan açıkça destek almadan özyinelemenin bir yeniden yazma kuralı biçiminde uygulanmasına izin verir. (src Wikipedia)


3

Bu operatör hayatınızı basitleştirebilir:

var Y = function(f) {
    return (function(g) {
        return g(g);
    })(function(h) {
        return function() {
            return f.apply(h(h), arguments);
        };
    });
};

Sonra ekstra fonksiyondan kaçının:

var fac = Y(function(n) {
    return n == 0 ? 1 : n * this(n - 1);
});

Sonunda ararsınız fac(5).


0

Bunu cevaplamanın en iyi yolunun JavaScript gibi bir dil seçmek olduğunu düşünüyorum:

function factorial(num)
{
    // If the number is less than 0, reject it.
    if (num < 0) {
        return -1;
    }
    // If the number is 0, its factorial is 1.
    else if (num == 0) {
        return 1;
    }
    // Otherwise, call this recursive procedure again.
    else {
        return (num * factorial(num - 1));
    }
}

Şimdi, fonksiyonun içindeki fonksiyonun adını kullanmamak için tekrar yazınız, ancak yine de yinelemeli olarak çağırınız.

İşlev adının factorialgörülmesi gereken tek yer çağrı sitesidir.

İpucu: işlevlerin adlarını kullanamazsınız, ancak parametre adlarını kullanabilirsiniz.

Problemi çöz. Sakın bakma. Çözdüğünüzde, y-birleştiricisinin hangi sorunu çözdüğünü anlayacaksınız.


1
Çözdüğünden daha fazla sorun yaratmadığından emin misiniz?
Noctis Skytower

1
Noctis, sorunuzu açıklığa kavuşturabilir misiniz? Bir y-birleştirici kavramının kendisinin çözdüğünden daha fazla sorun yaratıp yaratmadığını mı soruyorsunuz, yoksa özellikle JavaScript kullanarak göstermeyi seçtiğimden mi, yoksa özel uygulamamdan mı yoksa onu kendiniz keşfederek öğrenme önerimden mi bahsediyorsunuz? Tanımladım?
zumalifeguard
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.