Neden polimorfik tip `forall t: Tip, t-> t` olan bir fonksiyon kimlik fonksiyonu olmalı?


18

Programlama dili teorisinde yeniyim. Eğitmenin polimorfik tipte bir fonksiyonun forall t: Type, t->tkimlik olduğunu iddia ettiği ancak nedenini açıklamadığı bazı çevrimiçi dersleri izliyordum . Birisi bana nedenini açıklayabilir mi? Belki de ilk ilkelerden gelen iddianın kanıtı.


3
Bu sorunun yinelenmesi gerektiğini düşündüm, ama bulamıyorum. cs.stackexchange.com/questions/341/… bir tür takiptir . Standart referans ücretsiz Teoremler! Phil Wadler tarafından.
Gilles 'SO- kötü olmayı kes

1
Başka bir şey yapan bu tür ile genel bir işlev oluşturmaya çalışın. Hiç olmadığını göreceksiniz.
Bergi

@Bergi Evet Herhangi bir karşı örnek bulamadım, ancak yine de nasıl kanıtlayacağından emin değildim.
abhishek

Ama bir tane bulmaya çalıştığınızda gözlemleriniz nelerdi? Yaptığınız herhangi bir girişim neden işe yaramadı?
Bergi

@Giller Belki cs.stackexchange.com/q/19430/14663'ü hatırlıyor musunuz ?
Bergi

Yanıtlar:


32

Dikkat edilmesi gereken ilk şey, bunun mutlaka doğru olmadığıdır. Örneğin, dile bağlı olarak, bu tür bir işlev, kimlik işlevi olmanın yanı sıra şunları yapabilir: 1) sonsuza kadar döngü, 2) bir durumu değiştir, 3) geri dönme null, 4) bir istisna atma, 5) biraz G / Ç gerçekleştirme, 6) başka bir şey yapmak için bir iplik çatal, 7) call/ccshenanigans yapmak , 8) Java gibi bir şey kullanın Object.hashCode, 9) türün bir tamsayı olup olmadığını belirlemek için yansıma kullanın ve varsa artırın, 10) çağrı yığınını analiz etmek için yansıma kullanın ve içinde adlandırılan bağlama dayalı bir şey yapın, 11) muhtemelen başka birçok şey ve kesinlikle keyfi kombinasyonlar.

Yani buna yol açan özellik, parametriklik, bir bütün olarak dilin bir özelliğidir ve daha güçlü ve daha zayıf varyasyonları vardır. Tip teorisinde incelenen resmi taşların çoğu için yukarıdaki davranışların hiçbiri meydana gelemez. Örneğin, parametrikliğin ilk çalışıldığı Sistem F / saf polimorfik lambda hesabı için, yukarıdaki davranışların hiçbiri meydana gelemez. Bu sadece istisnalar, değişken durum, yok null, call/ccG / Ç, yansıma ve şiddetle sonsuza döngü olamaz bu yüzden normale ediyor. Gilles bir yorumda belirtildiği gibi, ücretsiz Teoremler kağıdı !Phil Wadler tarafından bu konuya iyi bir giriş niteliğindedir ve referansları teoriye, özellikle de mantıksal ilişkiler tekniğine doğru ilerleyecektir. Bu bağlantı ayrıca Wadler tarafından parametriklik konusundaki diğer makaleleri de listelemektedir.

Parametriklik dilin bir özelliği olduğundan, kanıtlamak için önce dili resmi olarak ifade etmeyi ve sonra nispeten karmaşık bir argümanı gerektirir. Polimorfik lambda hesabında olduğumuzu varsayarak bu özel durum için gayri resmi argüman t, girdi hakkında herhangi bir işlem yapamayacağımızdan (örneğin, artıramayız çünkü bir sayı) veya o türden bir değer oluşturun (bildiğimiz herkes için t= Void, hiç değeri olmayan bir tür). Bir tür değer üretmenin tek yolu tbize verilen değeri vermektir. Başka davranış mümkün değildir. Bunu görmenin bir yolu, güçlü normalleştirme kullanmak ve bu tipte sadece bir normal form terimi olduğunu göstermektir.


1
Sistem F, tür sisteminin algılayamadığı sonsuz döngülerden nasıl kaçındı? Bu, genel durumda çözümsüz olarak sınıflandırılır.
Joshua

2
@Joshua - durma problemi için standart imkansızlık kanıtı, ilk etapta sonsuz bir döngü olduğu varsayımı ile başlar. Bu nedenle, Sistem F'nin neden sonsuz döngüye sahip olmadığını sorgulamaya çağırmak dairesel akıl yürütmedir. Daha geniş anlamda, Sistem F neredeyse Turing'i tamamlamıyor, bu yüzden bu kanıtın varsayımlarını karşıladığından şüpheliyim. Bir bilgisayarın tüm programlarının sona erdiğini kanıtlaması için yeterince zayıftır (özyineleme yok, döngüler yok, döngüler için çok zayıf vb.).
Jonathan Cast

@Joshua: Genel durumda çözülemez, bu da birçok özel durumda çözülmesini engellemez. Özellikle, iyi yazılmış bir sistem F terimi olan her programın durduğu kanıtlanmıştır: tüm bu programlar için çalışan tek bir kanıt vardır. Açıkçası, bu , F sistemine
cody

15

İddianın kanıtı oldukça karmaşıktır, ancak gerçekten istediğiniz şeyse , Reynolds'un konuyla ilgili orijinal makalesine göz atabilirsiniz .

Anahtar fikir, polimorfik bir fonksiyonun gövdesinin, fonksiyonun tüm monomorfik örneklemeleri için aynı olduğu parametrik olarak polimorfik fonksiyonlar için geçerli olmasıdır. Böyle bir sistemde, polimorfik tipte bir parametrenin türü hakkında herhangi bir varsayım yapılamaz ve kapsamdaki tek değer genel bir türe sahipse, bununla ilgisi yoktur, onu döndürür veya diğer işlevlere geçirirsiniz ' ve tanımlanmış, bu da onu iade etmekten başka bir şey yapamaz .. .etc. Sonuç olarak, parametreyi geri göndermeden önce yapabileceğiniz tek şey bir kimlik zinciri zinciridir.


8

Derek'in bahsettiği tüm uyarılarla ve set teorisini kullanmaktan kaynaklanan paradoksları göz ardı ederek, Reynolds / Wadler'in ruhunda bir kanıt çizmeme izin verin.

Türün bir işlevi:

f :: forall t . t -> t

t tipi t ile indekslenen fonksiyon ailesidir .ftt

Fikir, polimorfik fonksiyonları resmi olarak tanımlamak için, tiplere bir değer kümesi olarak değil, ilişkiler olarak davranmamız gerektiğidir. IntEşitlik ilişkilerini indükleme gibi temel türler - örneğin, Inteşitse iki değer ilişkilidir. İşlevler, ilgili değerleri ilgili değerlerle eşleştiriyorsa ilişkilidir. İlginç durum polimorfik fonksiyonlardır. İlgili türleri ilgili değerlerle eşler.

fg

forall t . t -> t

stfsfssst to another value gt of the type tt. We say that f is related to g if the values fs and gt are related. Since these values are themselves functions, they are related if they map related values to related values.

The crucial step is to use the Reynolds' parametricity theorem, which says that any term is in a relation with itself. In our case, the function f is related to itself. In other words, if s is related to t, fs is also related to ft.

We can now pick any relation between any two types and apply this theorem. Let's pick the first type as the unit type (), which has only one value, also called (). We'll keep the second type t arbitrary but non-empty. Let's pick a relation between () and t to be simply one pair ((), c), where c is some value of the type t (a relation is just a subset of the cartesian product of sets). Parametricity theorem tells us that f() must be related to ft. They must map related values to related values. The first function f() doesn't have much choice, it must map the only value () back to (). Therefore the second function ft must map c to c (the only values related to ()). Since c is completely arbitrary, we conclude that ft is idt and, since t is completely arbitrary, f is id.

You can find more details in my blog.


-2

EDIT: A comment above has provided the missing piece. Some people are deliberately playing with less-than-turing-complete languages. I explicitly don't care about such languages. A really usuable not-turing-complete language is a crazy hard thing to design. The whole rest of this expands on what happens trying to apply these theorems to a full language.

False!

function f(a): forall t: Type, t->t
    function g(a): forall t: Type, t->t
       return (a is g) ? f : a
    return a is f ? g : a

where the is operator compares two variables for reference identity. That is, they contain the same value. Not an equivalent value, same value. Functions f and g are equivalent by some definition but they aren't the same.

If this function is passed itself it returns something else; otherwise it returns its input. The something else has the same type as itself therefore it can be substituted. In other words, f is not the identity, because f(f) returns g, whereas the identity would return f.

For the theorem to hold it has to assume the ridiculous ability to reduce

function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
    return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(1000, <0, a>)[1]¹

If you're willing to assume that you can assume the much easier type inference can be handled.

If we try to restrict the domain until the theorem holds we end up having to restrict it awfully far.

  • Pure Functional (no mutable state, no IO). OK I can live with that. A lot of time we want to run proofs over functions.
  • Empty standard library. meh.
  • No raise and no exit. Now we're starting to get constrained.
  • There is no bottom type.
  • The language has a rule that allows the compiler to collapse infinite recursion by assuming it must terminate. The compiler is allowed to reject trivial infinite recursion.
  • The compiler is allowed to fail if presented with something that can't be proved either way.² Now the standard library can't take functions as arguments. Boo.
  • There is no nil. This is starting to get problematic. We've ran out of ways to deal with 1/0.³
  • The language can't do branch type inferences and does not have an override for when the programmer can prove a type inference the language cant. This is pretty bad.

The existence of both of the last two constraints has crippled the language. While it is still Turing complete the only way to get general purpose work out of it is to simulate an inner platform that interprets a language with looser requirements.

¹ If you think the compiler can deduce that one, try this one

function fermat(z) : int -> int
    function pow(x, p)
        return p = 0 ? 1 : x * pow(x, p - 1)
    function f2(x, y, z) : int, int, int -> <int, int>
        left = pow(x, 5) + pow(y, 5)
        right = pow(z, 5)
        return left = right
            ? <x, y>
            : pow(x, 5) < right
                ? f2(x + 1, y, z)
                : pow(y, 5) < right
                    ? f2(2, y + 1, z)
                    : f2(2, 2, z + 1)
    return f2(2, 2, z)
function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
    return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(fermat(3)[0], <0, a>)[1]

² The proof that the compiler can't do this depends on blinding. We can use multiple libraries to ensure the compiler can't see the loop at once. Also, we can always construct something where the program would work but could not be compiled because the compiler can't perform the induction in available memory.

³ Somebody thinks you can have this return nil without arbitrary generic types returning nil. This pays a nasty penalty for which I have seen no effective language that can pay it.

function f(a, b, c): t: Type: t[],int,int->t
    return a[b/c]

must not compile. The fundamental problem is runtime array indexing doesn't work anymore.


@Bergi: I constructed a counterexample.
Joshua

1
Please take a moment to reflect on the difference between your answer and the other two. Derek's opening sentence is “The first thing to note is that this isn't necessarily true”. And then he explains what properties of a language make it true. jmite also explains what makes it true. In contrast, your answer gives an example in an unspecified (and uncommon language) with zero explanation. (What is the foil quantifier anyway?) This is not helpful at all.
Gilles 'SO- stop being evil'

1
@D.W: if a is f then the type of a is the type of f which is also type of g and therefore the typecheck should pass. If a real compiler kicked it out I would use the runtime cast that real languages always have for the static type system getting it wrong and it would never fail at runtime.
Joshua

2
That's not how a static typechecker works. It doesn't check that the types match for a single specific input. There are specific type rules, which are intended to ensure that the function will typecheck on all possible inputs. If you require use of a typecast then this solution is much less interesting. Of course if you bypass the type system then the type of a function guarantees nothing -- no surprise there!
D.W.

1
@D.W.: You miss the point. There is enough information for the static type checker to prove the code is type safe if it had the wit to find it.
Joshua
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.