Ocaml / F #’daki işlevler neden varsayılan olarak yinelemeli değil?


104

Neden F # ve Ocaml'daki (ve muhtemelen diğer dillerdeki) işlevler varsayılan olarak özyinelemeli değil?

Başka bir deyişle, neden dil tasarımcıları aşağıdaki recgibi bir bildirimi açıkça yazmanızı sağlamanın iyi bir fikir olduğuna karar verdi :

let rec foo ... = ...

ve varsayılan olarak işleve özyinelemeli yetenek vermiyor mu? Neden açık bir recyapıya ihtiyaç var ?


Yanıtlar:


87

Orijinal ML'nin Fransız ve İngiliz torunları farklı seçimler yaptı ve seçimleri on yıllar boyunca modern varyantlara miras kaldı. Yani bu sadece miras ama bu dillerdeki deyimleri etkiliyor.

İşlevler, Fransızca CAML dil ailesinde (OCaml dahil) varsayılan olarak yinelemeli değildir. Bu seçim, letbu dillerde kullanılan işlev (ve değişken) tanımlarının yerini almayı kolaylaştırır çünkü yeni bir tanımın gövdesi içinde önceki tanıma başvurabilirsiniz. F # bu sözdizimini OCaml'den devraldı.

Örneğin, OCaml'deki pbir dizinin Shannon entropisini hesaplarken işlevin yerini almak:

let shannon fold p =
  let p x = p x *. log(p x) /. log 2.0 in
  let p t x = t +. p x in
  -. fold p 0.0

pÜst düzey shannonişlev argümanının p, vücudun ilk satırında bir başkası ve ardından vücudun pikinci satırında bir başkası tarafından nasıl değiştirildiğine dikkat edin.

Tersine, ML ailesinin İngiliz SML şubesi diğer seçimi aldı ve funSML'nin -bound işlevleri varsayılan olarak özyinelemeli. Çoğu işlev tanımının, işlev adlarının önceki bağlamalarına erişmesi gerekmediğinde, bu daha basit kodla sonuçlanır. Ancak, superceded fonksiyonları (farklı adlar kullanmak için yapılır f1, f2kapsamını kirletir ve mümkün kılan vs.) yanlışlıkla bir fonksiyonun yanlış "sürümüne" çağırmak. Ve şimdi örtük olarak özyinelemeli funbağlı işlevler ile özyinelemeli olmayan bağlı işlevler arasında bir tutarsızlık var val.

Haskell, tanımlar arasındaki bağımlılıkları saf olmalarını kısıtlayarak çıkarmayı mümkün kılar. Bu, oyuncak örneklerini daha basit gösterir, ancak başka yerlerde ciddi bir maliyetle gelir.

Ganesh ve Eddie'nin verdiği cevapların kırmızı ringa balığı olduğuna dikkat edin. let rec ... and ...Tür değişkenleri genelleştirildiğinde etkilediği için neden işlev gruplarının bir devin içine yerleştirilemeyeceğini açıkladılar . Bunun recSML'de varsayılan olmakla ilgisi yoktur, ancak OCaml ile ilgisi yoktur .


3
Bunların kırmızı ringa balığı olduğunu sanmıyorum: eğer çıkarım üzerindeki kısıtlamalar olmasaydı, tüm programlar veya modüller otomatik olarak diğer dillerin çoğunda olduğu gibi karşılıklı olarak özyinelemeli olarak ele alınırdı. Bu, "rec" gerekip gerekmediğine dair özel tasarım kararını tartışmalı hale getirir.
GS - Monica'dan özür dile

"... diğer dillerin çoğunda olduğu gibi otomatik olarak karşılıklı yinelemeli olarak ele alınır." BASIC, C, C ++, Clojure, Erlang, F #, Factor, Forth, Fortran, Groovy, OCaml, Pascal, Smalltalk ve Standard ML yok.
JD

3
C / C ++ yalnızca ileriye dönük tanımlar için prototipler gerektirir, bu aslında özyinelemeyi açıkça işaretlemekle ilgili değildir. Java, C # ve Perl'in kesinlikle örtük özyinelemesi vardır. "Çoğu" kelimesinin anlamı ve her dilin önemi hakkında sonsuz bir tartışmaya girebiliriz, o halde "pek çok" diğer dile razı olalım.
GS - Monica'dan özür dile

3
"C / C ++ yalnızca ileriye dönük tanımlar için prototipler gerektirir, bu aslında özyinelemeyi açıkça işaretlemekle ilgili değildir". Sadece kendini yinelemenin özel durumunda. Karşılıklı özyineleme genel durumunda, ileri bildirimler hem C hem de C ++ için zorunludur.
JD

2
Aslında ileriye dönük bildirimler, sınıf kapsamlarında C ++ 'da gerekli değildir, yani statik yöntemler, herhangi bir bildirim olmadan birbirlerini çağırmak için iyidir.
polkovnikov.ph

52

Açık kullanımının önemli bir nedeni rec, tüm statik olarak yazılmış işlevsel programlama dillerinin (çeşitli şekillerde değiştirilmiş ve genişletilmiş olsa da) temelini oluşturan Hindley-Milner tipi çıkarım ile ilgilidir.

Bir tanımınız let f x = xvarsa, türüne sahip 'a -> 'aolmasını 'ave farklı noktalarda farklı türler için geçerli olmasını beklersiniz . Ama aynı şekilde, yazarsanız let g x = (x + 1) + ..., vücudunuzun geri kalanında xbir muamelesi görmeyi beklersiniz .intg

Hindley-Milner çıkarımının bu ayrımla ilgilenme şekli, açık bir genelleme adımından geçer. Programınızı işlerken belirli noktalarda, tür sistemi durur ve "tamam, bu noktada bu tanımların türleri genelleştirilecektir, böylece biri bunları kullandığında, türlerindeki herhangi bir serbest tür değişken yeni bir şekilde başlatılacaktır ve bu nedenle bu tanımın diğer kullanımlarına müdahale etmeyecektir. "

Bu genellemeyi yapmak için mantıklı yerin, karşılıklı olarak özyinelemeli bir dizi işlevi kontrol ettikten sonra olduğu ortaya çıktı. Daha erken olursa, çok fazla genelleme yaparsınız, türlerin gerçekten çarpışabileceği durumlara yol açarsınız. Daha sonra, çok az genelleme yaparsınız, çoklu tip örneklerle kullanılamayan tanımlar yaparsınız.

Öyleyse, tür denetleyicisinin hangi tanım kümelerinin karşılıklı olarak yinelemeli olduğunu bilmesi gerektiği düşünüldüğünde, ne yapabilir? Bir olasılık, bir kapsamdaki tüm tanımlar üzerinde basitçe bir bağımlılık analizi yapmak ve bunları mümkün olan en küçük gruplar halinde yeniden sıralamaktır. Haskell aslında bunu yapıyor, ancak kısıtlanmamış yan etkileri olan F # (ve OCaml ve SML) gibi dillerde bu kötü bir fikir çünkü yan etkileri de yeniden sıralayabilir. Bunun yerine, kullanıcıdan hangi tanımların karşılıklı olarak yinelemeli olduğunu açıkça işaretlemesini ve dolayısıyla genellemenin nerede olması gerektiğini genişletmesini ister.


3
Err, hayır. İlk paragrafınız yanlış ("ve" nin açık kullanımından bahsediyorsunuz, "rec" değil) ve sonuç olarak geri kalanı alakasız.
JD

5
Bu gereksinimden asla memnun kalmadım. Açıklama için teşekkürler. Haskell'in tasarımda üstün olmasının bir başka nedeni.
Bent Rasmussen

9
HAYIR!!!! BU NASIL OLABİLİR ?! Bu cevap düpedüz yanlış! Lütfen aşağıdaki Harrop'un yanıtını okuyun veya Standart
Makine Öğreniminin Tanımı'na bakın

9
Cevabımda da söylediğim gibi, tip çıkarımı sorunu, tek neden değil, kayıt ihtiyacının nedenlerinden biridir . Jon'un cevabı da çok geçerli bir cevaptır (Haskell hakkında olağan küçümseyici yorum dışında); İkisinin karşıt olduğunu sanmıyorum.
GS - Monica'dan özür dile

16
"tür çıkarımı sorunu, kayıt ihtiyacının nedenlerinden biridir". OCaml'ın gerektirdiği recancak SML'nin olmadığı gerçeği bariz bir karşı örnektir. Tanımladığınız nedenlerden dolayı tür çıkarımı sorun olsaydı, OCaml ve SML onlar gibi farklı çözümler seçemezdi. Sebep, elbette, andHaskell'i alakalı kılmak için bahsetmenizdir .
JD

10

Bunun iyi bir fikir olmasının iki temel nedeni vardır:

İlk olarak, özyinelemeli tanımları etkinleştirirseniz, aynı adlı bir değerin önceki bir bağına başvuramazsınız. Mevcut bir modülü genişletmek gibi bir şey yaparken bu genellikle kullanışlı bir deyimdir.

İkincisi, yinelemeli değerler ve özellikle karşılıklı olarak yinelemeli değer kümeleri, o zaman sırayla ilerleyen tanımların, her yeni tanımın zaten tanımlanmış olanın üzerine inşa edildiği akıl yürütmek için çok daha zordur. Bu tür bir kodu okurken, açıkça özyinelemeli olarak işaretlenen tanımlar dışında, yeni tanımların yalnızca önceki tanımlara atıfta bulunabileceğinin garantisine sahip olmak güzeldir.


4

Bazı tahminler:

  • letyalnızca işlevleri bağlamak için değil, aynı zamanda diğer normal değerleri de bağlamak için kullanılır. Çoğu değer biçiminin yinelemeli olmasına izin verilmez. Belirli özyinelemeli değer biçimlerine izin verilir (örn. İşlevler, tembel ifadeler, vb.), Bu nedenle bunu belirtmek için açık bir sözdizimine ihtiyaç duyar.
  • Yinelemeli olmayan işlevleri optimize etmek daha kolay olabilir
  • Özyinelemeli bir işlev oluşturduğunuzda oluşturulan kapanış, işlevin kendisine işaret eden bir girdi içermelidir (böylece işlev kendini yinelemeli olarak çağırabilir), bu da özyinelemeli kapanışları yinelemeli olmayan kapanışlardan daha karmaşık hale getirir. Dolayısıyla, özyinelemeye ihtiyacınız olmadığında daha basit özyinelemeli olmayan kapanışlar oluşturabilmek güzel olabilir.
  • Önceden tanımlanmış bir işlev veya aynı adın değeri açısından bir işlev tanımlamanıza olanak tanır; bunun kötü bir uygulama olduğunu düşünmeme rağmen
  • Ekstra güvenlik? Amaçladığınız şeyi yaptığınızdan emin olur. Örneğin, özyinelemeli olmasını istemiyorsanız, ancak yanlışlıkla işlevin kendisiyle aynı adı taşıyan bir adı kullandıysanız, büyük olasılıkla şikayet edecektir (ad daha önce tanımlanmamışsa)
  • letYapı benzer letLisp ve Şema konstruktu; yinelemeli olmayan. letrecÖzyinelemeli hadi yapalım için Scheme'de ayrı bir yapı var

"Çoğu değer biçiminin özyinelemeli olmasına izin verilmez. Belirli özyinelemeli değer biçimlerine izin verilir (ör. İşlevler, tembel ifadeler, vb.), Bu nedenle bunu belirtmek için açık bir sözdizimine ihtiyaç duyar". Bu F # için doğrudur, ancak yapabileceğiniz OCaml için ne kadar doğru olduğundan emin değilim let rec xs = 0::ys and ys = 1::xs.
JD

4

Bu göz önüne alındığında:

let f x = ... and g y = ...;;

Karşılaştırmak:

let f a = f (g a)

Bununla:

let rec f a = f (g a)

Eski Yeniden Tanımlıyor fönceden tanımlanmış uygulamak fuygulanması sonucuna ggöre a. İkincisi Yeniden Tanımlıyor fdöngü sonsuza uygulayarak giçin aML varyantları istediğini genellikle olmadığı.

Bununla birlikte, bu bir dil tasarımcısı tarzı şeydir. Sadece onunla git.


1

Bunun büyük bir kısmı, programcıya yerel kapsamlarının karmaşıklığı üzerinde daha fazla kontrol sağlamasıdır. Spektrumu let, let*ve let recteklif güç ve maliyet hem artan seviyesi. let*ve let recözünde basit letolanın iç içe geçmiş sürümleridir , bu nedenle herhangi birini kullanmak daha pahalıdır. Bu derecelendirme, elinizdeki görev için ihtiyaç duyduğunuz izin düzeyini seçebildiğiniz için programınızın optimizasyonunu mikro yönetmenize olanak tanır. Özyinelemeye veya önceki bağlamalara başvurma yeteneğine ihtiyacınız yoksa, biraz performanstan tasarruf etmek için basit bir izne geri dönebilirsiniz.

Scheme'deki kademeli eşitlik tahminlerine benzer. (yani eq?, eqv?ve equal?)

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.