Neden çoğu programlama dili fonksiyondan sadece tek bir değer döndürmeyi destekliyor? [kapalı]


118

Çoğu (?) Programlama dilindeki fonksiyonların herhangi bir sayıda giriş parametresini değil, sadece bir dönüş değerini destekleyecek şekilde tasarlanmasının bir nedeni var mı?

Çoğu dilde, bu sınırlamayı "dış parametreler kullanarak, işaretçileri döndürerek veya yapı / sınıfları tanımlayarak / geri getirerek" çözmek mümkündür. Ancak programlama dillerinin çoklu dönüş değerlerini daha "doğal" bir şekilde desteklemek için tasarlanmamış olmaları garip görünüyor.

Bunun bir açıklaması var mı?


40
çünkü bir dizi döndürebilirsin ...
nathan hayfield

6
Peki neden sadece bir argümana izin vermiyor? Konuşmaya bağlı olduğundan şüpheleniyorum. Bazı fikirleri alın, onları dinleyebilecek kadar şanslı / talihsiz olana geri döndürdüğünüz bir şeye sokun. Geri dönüş neredeyse bir fikir gibidir. "bu", "bunlarla" yaptığınız şeydir.
Erik Reppen

30
Python'un yaklaşımının oldukça basit ve şık olduğuna inanıyorum: birden fazla değer döndürmek zorunda kaldığınızda basitçe bir tuple döndürün: def f(): return (1,2,3)ve sonra tuple "bölmek" için tuple-unpacking komutunu kullanabilirsiniz a,b,c = f() #a=1,b=2,c=3. Dizi oluşturmaya ve elemanları elle çıkartmaya gerek yok, yeni bir sınıf tanımlamaya gerek yok.
Bakuriu,

7
Matlab'ın değişken sayıda geri dönüş değeri olduğunu size söyleyebilirim. Çıkış argümanlarının sayısı arayan imza ile belirlenir (örn. [a, b] = f()Vs. [a, b, c] = f()) ve içinde elde fedilir nargout. Ben büyük bir Matlab hayranı değilim ama bu aslında oldukça kullanışlı oluyor.
gerrit

5
Çoğu programlama dili bu şekilde tasarlanmışsa, tartışılabilir olduğunu düşünüyorum . Programlama dilleri tarihinde, bu şekilde yaratılmış çok popüler bazı diller vardı (Pascal, C, C ++, Java, klasik VB gibi), ancak bugün çok sayıda geri dönüşe izin veren daha fazla hayran alan çok sayıda başka dil var. değerler.
Doktor Brown,

Yanıtlar:


58

Bazı diller, Python gibi bazı diller ise, doğal olarak birden dönüş değerleri destekleyen C # gibi onların temel kütüphaneleri aracılığıyla onlara destek.

Ancak genel olarak, onları destekleyen dillerde bile, özensiz oldukları için çok sayıda dönüş değeri sık kullanılmaz:

  • Birden çok değer döndüren işlevlerin açıkça adlandırılması zordur .
  • Dönüş değerlerinin sırasını yanlış yapmak kolaydır

    (password, username) = GetUsernameAndPassword()  
    

    (Bu aynı sebepten dolayı, birçok kişi bir fonksiyon için çok fazla parametre kullanmaktan kaçınır; bazıları bile bir fonksiyonun asla aynı tipte iki parametreye sahip olmaması gerektiğini söyler)

  • OOP dilleri zaten birden fazla dönüş değerleri için daha iyi bir alternatif var: sınıflarını.
    Daha güçlü şekilde yazılırlar, dönüş değerlerini tek bir mantıksal birim olarak gruplandırırlar ve dönüş değerlerinin adlarını tüm kullanımlarda tutarlı tutarlar.

Onlar tek bir yerde olan oldukça kullanışlı bir fonksiyonu birden fazla dönüş değerleri birbirine birden fazla giriş parametreleri olarak kullanılabilir (Python gibi) dilde. Ancak bunun sınıf kullanmaktan daha iyi bir tasarım olduğu kullanım durumları oldukça zayıf.


50
Bir demeti geri getirmenin çok şeyi iade ettiğini söylemek zor. Bir demet döndürüyor . Yazdığınız kod sadece bazı sözdizimsel şeker kullanarak temiz bir şekilde açar.

10
@Lego: Bir ayrım görmüyorum - bir tuple tanım gereği çoklu değerlerdir. Eğer değilse, "çoklu getiri değerleri" olarak ne düşünürsünüz?
BlueRaja - Danny Pflughoeft

19
Bu gerçekten puslu bir ayrım, ancak boş bir Tuple düşünün (). Bu bir şey mi, sıfır şey mi? Şahsen, onun tek bir şey olduğunu söyleyebilirim. Tam olarak atayabilirim x = (), aynen atayabildiğim gibi x = randomTuple(). İkincisinde, geri dönen demet boşsa ya da olmasa da, geri verilen parantezi yine de atayabilirim x.

19
... Hiçbir zaman başkalarının kullanmayacağını iddia etmedim. Ama savunarak "Bu dizilerini destekler Python çoklu dönüş değeri desteklemez" basitçe ediliyor derece amaçsızca bilgiçlik. Bu hala doğru bir cevap.
BlueRaja - Danny Pflughoeft

14
Ne tekiller ne de sınıflar "çoklu değer" değil.
Andres F.

54

Çünkü fonksiyonlar bir hesaplama yapan ve bir sonuç veren matematiksel yapılardır. Nitekim, birkaç programlama dilinin "başlığı altında" olanların çoğu, sadece bir girişe ve bir çıkışa odaklanır, çoklu girişler girişin etrafında sadece ince bir sargı yapar ve tek bir değer çıkışı çalışmadığında, tek bir Yapışkan yapı (veya demet veya Maybe) çıktı ("tek" dönüş değerinin birçok değerden oluşmasına rağmen).

Bu değişmedi, çünkü programcılar outparametreleri yalnızca sınırlı bir senaryo dizisinde faydalı olan garip yapılar olarak buldular . Diğer birçok şey gibi, destek de yok çünkü ihtiyaç / talep orada değil.


5
@FrustratedWithFormsDesigner - Bu son bir soruya biraz geldi . Bir yandan, 20 yılda birden fazla çıktı almak istediğimin sayısını sayabilirim.
Telastyn

61
Matematikteki İşlevler ve çoğu programlama dilindeki işlevler çok farklı iki canavardır.
tdammers

17
İlk günlerde Tdammers, düşünceye çok benziyorlardı. Fortran, pascal ve benzeri matematiğin bilgisayar mimarisinden çok daha fazla etkilendiği yerler.

9
@tdammers - nasıl? Demek istediğim, çoğu dil için sonunda lambda hesabına kadar kaynar - bir giriş, bir çıkış, yan etki yok. Geriye kalan her şey bunun üzerine bir simülasyon / kesmek. Programlama fonksiyonları, çoklu girdilerin aynı çıktıyı verebilmesi anlamında saf olmayabilir, ancak ruh oradadır.
Telastyn

16
@SteveEvers: "işlev" adının, daha uygun "prosedür" veya "rutin" yerine, zorunlu programlamada devralması talihsiz bir durumdur. İşlevsel programlamada, bir işlev Matematiksel işlevlere çok daha fazla benzemektedir.
tdammers

35

Matematik olarak, bir "iyi tanımlanmış" fonksiyonu , belirli bir girdi için sadece 1 çıkışı (bir yan not olarak, sadece tek bir giriş fonksiyonları vardır ve yine semantik kullanarak birden fazla giriş almak olduğu yerde biridir -işlemden ).

Çok değerli fonksiyonlar için (örneğin, pozitif bir tamsayıya ait kare kökü), bir koleksiyon veya değer dizisini döndürmek yeterlidir.

Bahsettiğiniz işlev türleri için (yani, birden fazla değer döndüren işlevler , farklı türlerde ) Gördüğünüzden biraz farklı görüyorum: Daha iyi bir tasarım ya da daha kullanışlı veri yapısı. Örneğin, *.TryParse(...)yöntemlerin Maybe<T>bir dış param kullanmak yerine bir monad döndürmesini tercih ederim . F # 'daki bu kodu düşünün:

let s = "1"
match tryParse s with
| Some(i) -> // do whatever with i
| None -> // failed to parse

Derleyici / IDE / analiz desteği bu yapılar için çok iyidir. Bu, params için "ihtiyaç" nin çoğunu çözer. Tamamen dürüst olmak gerekirse, bunun çözüm olmayacağı herhangi bir başka metodu düşünemiyorum.

Diğer senaryolar için - hatırlayamadığım şeyler - basit bir demet yeterlidir.


1
Ek olarak, C #: ' var (value, success) = ParseInt("foo");da yazabilmek isterdim, çünkü derleme zamanı tipi kontrol edildi, çünkü (int, bool) ParseInt(string s) { }ilan edildi. Bunun jeneriklerle yapılabileceğini biliyorum , ancak yine de güzel bir dil eklenmesi için yeterli olacaktır.
Umutsuzluğun Suçu

10
@GrimaceofDespair gerçekte istediğin şey, çoklu dönüş değerlerini değil, sözdizimini yok etmektir.
Domenic

2
@ warren: Evet. Buraya bakın ve böyle bir çözümün sürekli olmayacağına dikkat edin: en.wikipedia.org/wiki/Well-definition
Steven Evers

4
İyi tanımlanmışlığın matematiksel kavramının, bir fonksiyonun döndürdüğü çıktıların sayısıyla ilgisi yoktur. Giriş aynıysa çıkışların her zaman aynı olacağı anlamına gelir. Kesin konuşursak, matematiksel işlevler bir değer döndürür, ancak bu değer genellikle bir değerdir. Bir matematikçiye göre, temelde bununla birden fazla değer döndürme arasında bir fark yoktur. Programlama işlevlerinin yalnızca bir değer döndürmesi gerektiği için argümanlar, çünkü matematik işlevlerinin zorlayıcı olmadığı şeydir.
Michael Siler

1
@MichaelSiler (Yorumunuzla aynı fikirdeyim) Ama lütfen argümanın geri dönüşümlü olduğunu unutmayın: "Program işlevlerinin birden fazla değeri döndürebildiği için tek bir tuple değerini döndürebilirler çünkü çok zorlayıcı değildir" :)
Andres F.

23

Bir işlev döndüğünde montajda kullanılan paradigmalara baktığınızda söylenenlere ek olarak, belirli bir kayıt defterinde dönen nesneye bir işaretçi bırakır. Değişken / çoklu kayıt kullanmışlarsa çağıran fonksiyon, eğer bu fonksiyon bir kütüphanede olsaydı, döndürülen değerlerin nereden alınacağını bilemezdi. Bu da kütüphanelerle bağlantı kurmayı zorlaştıracak ve keyfi bir sayıda iade edilebilir işaretçi koymak yerine bir taneyle birlikte gideceklerdi. Daha yüksek seviyeli diller aynı bahaneye sahip değil.


Ah! Çok ilginç ayrıntı. 1!
Steven Evers

Bu kabul edilen cevap olmalı. Genellikle insanlar derleyiciler oluştururken hedef makineleri düşünürler. Diğer bir benzetme neden int, float, char / string, vb. Olduğumuzdur. Çünkü hedef makine tarafından desteklenen budur. Hedef çıplak metal olmasa bile (örneğin jvm), çok fazla taklit etmeden hala iyi bir performans elde etmek istersiniz.
imel96

26
... Bir fonksiyondan çoklu değerleri döndürmek için bir arama kuralını kolayca tanımlayabilirsiniz, neredeyse aynı şekilde bir fonksiyona birden fazla değer iletmek için bir arama kuralları tanımlayabilirsiniz. Bu bir cevap değil. -1
BlueRaja - Danny Pflughoeft

2
Yığın tabanlı yürütme motorlarının (JVM, CLR) çoklu geri dönüş değerleri dikkate alıp almadığını bilmek ilginç olurdu. Oldukça kolay olmalı; Arayanın sadece doğru sayıda argüman alması gerekir, tıpkı doğru sayıda argümanı zorladığı gibi!
Lorenzo Dematté

1
@David no, cdecl (teorik olarak) sınırsız sayıda parametreye izin verir ( varargs fonksiyonlarının mümkün olmasının nedeni budur ) . Her ne kadar bazı C derleyicileri sizi işlev başına birkaç düzine veya yüz argümanla sınırlayabilseler de, bence hala makul olandan daha fazlasıdır. -_-
BlueRaja - Danny Pflughoeft

18

Geçmişte çoklu getiri değerleri kullanmış olacağınız birçok kullanım durumu, artık modern dil özellikleri için gerekli değildir. Bir hata kodu döndürmek ister misiniz? Bir istisna at veya geri dön Either<T, Throwable>. İsteğe bağlı bir sonuç döndürmek ister misiniz? Bir dönüş Option<T>. Birkaç türden birini iade etmek ister misiniz? Bir İade Either<T1, T2>veya etiketli birliği.

Ve gerçekten birden fazla değer döndürmeniz gereken durumlarda bile , modern diller genellikle çemberleri veya bir tür veri yapısını (liste, dizi, sözlük) ya da nesneleri, ayrıca ambalajlamayı oluşturan bir çeşit bağlama ya da kalıp eşleştirme biçimini destekler. çoklu değerlerinizi tek bir değere dönüştürün ve ardından tekrar önemsiz olarak çoklu değerlere imha edin.

İşte dillerin birkaç örnek yok birden çok değeri dönen destekler. Birden fazla getiri değeri için destek eklemenin onları yeni bir dil özelliğinin maliyetini dengelemek için nasıl daha anlamlı hale getireceğini gerçekten anlamıyorum.

Yakut

def foo; return 1, 2, 3 end

one, two, three = foo

one
# => 1

three
# => 3

piton

def foo(): return 1, 2, 3

one, two, three = foo()

one
# >>> 1

three
# >>> 3

Scala

def foo = (1, 2, 3)

val (one, two, three) = foo
// => one:   Int = 1
// => two:   Int = 2
// => three: Int = 3

Haskell

let foo = (1, 2, 3)

let (one, two, three) = foo

one
-- > 1

three
-- > 3

Perl6

sub foo { 1, 2, 3 }

my ($one, $two, $three) = foo

$one
# > 1

$three
# > 3

1
Bir yönünün, bazı dillerde (Matlab gibi) bir fonksiyonun kaç değer döndürdüğü konusunda esnek olabileceğini düşünüyorum; yukarıdaki yorumuma bakın . Matlab'da sevmediğim pek çok özellik var, ancak bu Matlab'dan örneğin Python'a taşırken özlediğim birkaç (belki de sadece) özellikten biri.
gerrit

1
Peki ya Python veya Ruby gibi dinamik diller? Farz sortişlevi gibi bir şey yazdığımı varsayalım : sorted = sort(array)yalnızca sıralanmış diziyi [sorted, indices] = sort(array)döndürür, ikisini de döndürür. Python'da düşünebilmemin tek yolu, bayraklarını ya da sortçizgileri boyunca geçirmektir . sort(array, nout=2)sort(array, indices=True)
gerrit

2
@ MikeCellini Sanmıyorum. Bir fonksiyon, fonksiyonun ne kadar çıktı argümanıyla ( [a, b, c] = func(some, thing)) çağrıldığını söyleyebilir ve buna göre hareket eder. Bu, örneğin, ilk çıktı argümanını hesaplamak ucuzsa, ikincisini hesaplamak pahalı ise kullanışlıdır. Matlabs eşdeğeri nargoutçalışma zamanında mevcut olan başka bir dil bilmiyorum .
gerrit

1
@gerrit Python doğru çözüm bu yazmaktır: sorted, _ = sort(array).
Miles Rout,

1
@MilesRout: sortİşlev, endeksleri hesaplamak zorunda olmadığını söyleyebilir mi? Çok havalı, bunu bilmiyordum.
Jörg W Mittag

12

Tek bir getiri değerinin bu kadar popüler olmasının asıl nedeni, birçok dilde kullanılan ifadelerdir. Sizin gibi bir ifadeye sahip olabileceğiniz herhangi bir dilde x + 1, zaten tek dönüş değerleri olarak düşünürsünüz, çünkü kafanızdaki bir ifadeyi parçalara ayırarak ve her bir parçanın değerini belirleyerek değerlendirirsiniz. Sen bakmak xve onu değer (örneğin) 3 mi olduğuna karar ve 1 bakmak ve sonra bakmakx + 1ve bütünün değerinin 4 olduğuna karar vermek için hepsini bir araya getirin. İfadenin her sözdizimsel kısmı bir başka değere değil bir değere sahiptir; Bu, herkesin beklediği ifadelerin doğal anlambilimidir. Bir işlev bir çift değer döndürse bile, gerçekten iki değerin işini yapan bir değeri döndürür, çünkü bir şekilde tek bir koleksiyona sarılı olmayan iki değeri döndüren bir işlev fikri çok gariptir.

İnsanlar, fonksiyonların birden fazla değere sahip olması için gerekli olan alternatif anlambilim ile uğraşmak istemiyorlar. Örneğin, Forth gibi bir yığın temelli dilde , herhangi bir sayıda dönüş değerine sahip olabilirsiniz, çünkü her bir fonksiyon basitçe yığının tepesini değiştirir, girişleri fırlatır ve çıkışları istediği zaman iter. Bu nedenle Forth'un normal dillerin ifade ettiği türden ifadeleri yok.

Perl , bazen işlevler gibi davranabilen başka bir dildir, genellikle sadece bir listeyi döndürdüğü düşünüldüğü halde, birden fazla değer döndürür. Perl'deki "enterpolate" listelerinin yolu bize (1, foo(), 3)Perl'i beklemeyen çoğu insanın 3 öğeye sahip olabileceği, ancak buna bağlı olarak sadece 2 öğeye, 4 öğeye veya daha fazla öğeye sahip olabileceği gibi listeler . foo(). Perl'deki listeler düzleştirilir, böylece sözdizimsel bir liste her zaman bir listenin semantiğine sahip olmaz; sadece daha büyük bir listenin parçası olabilir.

İşlevlerin birden çok değer döndürmesini sağlamanın başka bir yolu, herhangi bir ifadenin birden çok değere sahip olabileceği ve her değerin bir olasılığı temsil ettiği alternatif bir ifade semantiğine sahip olmak olacaktır. x + 1Tekrar ele alın , ancak bu kez xiki değerin {3, 4} olduğunu, sonra değerlerinin x + 1{4, 5} x + xolacağını ve değerlerinin {6, 8} veya belki {6, 7, 8} olacağını hayal edin. , bir değerlendirmenin birden fazla değer kullanmasına izin verilip verilmediğine bağlı olarak x. Bunun gibi bir dil, Prolog'un bir sorguya birden çok cevap vermek için kullandığı gibi geri izleme kullanılarak uygulanabilir .

Kısacası, bir işlev çağrısı tek bir sözdizimsel birimdir ve tek bir sözdizimsel birim, bildiğimiz ve sevdiğimiz ifade anlamında tek bir değere sahiptir. Başka herhangi bir anlambilim sizi Perl, Prolog veya Forth gibi şeyler yapmanın garip yollarına zorlar.


9

Bu cevapta önerildiği gibi, bu donanım desteği konusudur, ancak dil tasarımındaki gelenek de rol oynar.

bir işlev döndüğünde, belirli bir kayıt defterinde dönen nesneye bir işaretçi bırakır

İlk üç dilden Fortran, Lisp ve COBOL, birincisi matematiğe göre modellenirken tek bir geri dönüş değeri kullandı. İkincisi, rastgele sayıda parametreyi, aldığı şekilde değiştirdi: liste halinde (yalnızca geçtiği ve tek bir parametre döndürdüğü de iddia edilebilir: listenin adresi). Üçüncüsü sıfır veya bir değer döndürür.

Bu ilk diller, onları takip eden dillerin tasarımını çok etkiledi, ancak birden fazla değer veren tek, Lisp hiçbir zaman popüler olmadı.

C geldiğinde, ondan önceki dillerden etkilenirken, donanım kaynağının verimli kullanılmasına ve C dilinin ne yaptığı ile onu uygulayan makine kodu arasında yakın bir ilişki kurmaya odaklandı. "Auto" vs "register" değişkenleri gibi en eski özelliklerinden bazıları, bu tasarım felsefesinin bir sonucudur.

Ayrıca derleme dilinin, ana akım gelişimin dışına çıkmaya başladığı 80'li yıllara kadar yaygın bir şekilde popüler olduğu belirtilmelidir. Derleyiciler yazan ve dilleri oluşturan insanlar , derleme hakkında bilgi sahibi oldular ve çoğunlukla orada en iyi olanı yapmaya devam ettiler.

Bu normdan ayrılan dillerin çoğu hiçbir zaman fazla popülerlik bulamadı ve bu nedenle, dil tasarımcılarının (elbette bildiklerinden ilham alan) kararlarını etkileyen güçlü bir rol oynamadılar.

Öyleyse gidip derleme dilini inceleyelim. Önce Apple II ve VIC-20 mikrobilgisayarları tarafından kullanılan bir 1975 mikroişlemci olan 6502'ye bakalım . Programlama dillerinin şafağında, 20, 30 yıl önceki ilk bilgisayarlara kıyasla daha güçlü olmasına rağmen, zamanın anabilgisayarında ve minibilgisayarlarında kullanılanlarla karşılaştırıldığında çok zayıftı.

Teknik açıklamaya bakarsanız, 5 yazmaç artı birkaç tane bitlik bayrak bulunur. Tek "tam" kayıt programı Program Sayacı (PC) idi - kayıt işlemi yapılacak bir sonraki talimatı işaret ediyor. Diğer, akümülatörün (A), iki "endeks" in (X ve Y) ve bir yığın göstergesinin (SP) kaydedildiği yerde saklanır.

Alt yordamın çağrılması, bilgisayarı SP'nin belirttiği belleğe koyar ve ardından SP'yi azaltır. Bir alt rutinden geri dönüş ters çalışır. Biri yığındaki diğer değerleri itip çekebilir, ancak SP'ye göre belleğe başvurmak zordur, bu nedenle yeniden giren alt programları yazmak zordu. Aldığımız, istediğimiz zaman bir alt rutin olarak adlandırdığımız bu mimari bu kadar yaygın değildi. Genellikle, ayrı bir "yığın" yaratılır, böylece parametreler ve alt rutin geri dönüş adresi ayrı tutulur.

Eğer 6502, ilham işlemci bakacak olursak 6800 , bu SP değeri alabileceği ilave bir kayıt, Index Kayıt (IX), kadar geniş SP vardı.

Makinede, yeniden giriş yapan bir alt yordamı çağırmak, parametreleri istifte zorlamak, PC'yi itmek, PC'yi yeni adrese değiştirmek ve ardından alt yordamı yerel değişkenleri yığında zorlamaktan ibaretti . Yerel değişkenlerin ve parametrelerin sayısı bilindiğinden, bunları adresleme yığına göre yapılabilir. Örneğin, iki parametre alan ve iki yerel değişkene sahip bir işlev şöyle görünür:

SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1

Tüm geçici boşluk yığında olduğundan, herhangi bir sayıda çağrılabilir.

8080 dolaylı sicil, HL üzerine haşhaş sonra yığın SP bastırıyor ve tarafından, 6800 benzer bir şey yapabileceğini TRS-80 ve CP / M tabanlı mikro bilgisayarların bir ana bilgisayarda kullanılan.

Bu, işleri uygulamak için çok yaygın bir yöntemdir ve daha kolay dönmeden önce tüm yerel değişkenleri terk etmeyi sağlayan Base Pointer ile daha modern işlemciler konusunda daha da fazla destek aldı.

Sorun, bir şeyi nasıl iade edersiniz ? İşlemci kayıtları çok erken sayılmadı ve çoğu zaman hangi bellek parçasını ele alacağını bulmak için bile bazılarını kullanmak gerekiyordu. Yığındaki şeylerin geri dönüşü karmaşık olacaktır: her şeyi açmanız, bilgisayarı kaydetmeniz, geri dönüş parametrelerini itmeniz (bu arada nerede saklanır?), Sonra bilgisayarı tekrar itip geri dönmeniz gerekir.

Genelde yapılan şey , geri dönüş değeri için bir kayıt ayırmaktı . Çağıran kod, dönüş değerinin belirli bir sicilde olacağını, kaydedilmesi veya kullanılması için korunmasının gerekli olacağını biliyordu.

Birden fazla dönüş değerine izin veren bir dile bakalım: İleri. Forth'un yaptığı, ayrı bir geri dönüş yığını (RP) ve veri yığını (SP) tutmak, böylece bir işlevin yapması gereken, tüm parametrelerini açıp geri dönüş değerlerini yığında bırakmaktı. Dönüş yığını ayrı olduğundan, yoluna girmedi.

Bilgisayarlarda edinilen ilk altı aylık dönemde, montaj dilini ve İleri'yi öğrenen biri olarak, çoklu iade değerleri benim için tamamen normal görünüyor. /modTam sayıyı ve geri kalanını döndüren Forth's gibi operatörler açık görünüyor. Öte yandan, ilk deneyimleri C zihin olan birinin bu kavramı nasıl tuhaf bulduğunu kolayca görebiliyorum: bir "fonksiyonun" ne olduğu konusundaki yerleşik beklentilerine aykırı.

Matematik gelince ... pekala, matematik derslerinde fonksiyonlara başlamadan önce bilgisayarları programlıyordum. Orada olduğu değil bütün bir bölüm bulunmaktadır, sonra tekrar, CS ve matematik etkilenir programlama dillerinin bir bölüm ama.

Bu nedenle matematiğin erken dil tasarımını etkilediği, donanım kısıtlamalarının kolaylıkla uygulanabilen şeyleri dikte ettiği ve popüler dillerin donanımın nasıl geliştiğini etkilediği (Lisp makinesi ve Forth makine işlemcileri bu süreçte yol tutkunlarıydı) faktörlerin birleştiğine sahibiz.


@gnat "Temel bilgi sağlamak için" olduğu gibi "bilgilendirmek" kullanımı kasıtlıydı.
Daniel C. Sobral

Bu konuda şiddetle hissediyorsanız geri alma konusunda çekinmeyin; Benim okuma başına etkisi burada biraz daha iyi uyuyor: "etkiler ... önemli bir şekilde"
sivrisineği

1
+1 Belirttiğine göre, az sayıda CPU'nun, mevcut CPU'ların (örneğin, x64 abi gibi birçok ABI'de de kullanılır), örneğin x64 abi'de kullanılanlarla) karşılaştırıldığında, ilk CPU'ların sayısız kaydının bir oyun değiştirici olabileceği ve eskiden sadece 1 değer getirmesinin sebepleri bugünlerde sadece tarihi bir sebep olabilir.
BitTickler

8 bitlik mikro işlemcilerin, dil tasarımı ve herhangi bir mimaride C veya Fortran arama sözleşmelerinde yapılması gerekenlerin varsayıldığı konusunda çok etkili olduğuna ikna olmadım. Fortran, dizi argümanlarını (esas olarak işaretçileri) geçebileceğinizi varsayar. Bu yüzden, zaten cevaplarınızda ve C: Z80 derleyicileri neden zayıf kod üretiyor? retrocomputing.SE üzerinde.
Peter Cordes

Fortran, C gibi, aynı zamanda keyfi bir dizi argüman iletebileceğinizi ve bunlara rasgele erişebildiğinizi ve rasgele bir miktarda yerliler alabileceğinizi varsayıyor, değil mi? Sadece açıklandığı gibi, olamaz yığın göreli adresleme bir şey değildir çünkü evresel terk sürece, 6502 tarihinde kolayca bunu. Bunları statik depolamaya sokabilirsiniz. İsteğe bağlı bir argüman listesi iletebilirseniz, kayıtlara uymayan dönüş değerleri için (örneğin ilk değerin ötesinde) ekstra gizli parametreler ekleyebilirsiniz.
Peter Cordes

7

Tanıdığım işlevsel diller, tuple kullanımıyla kolayca birden fazla değer döndürebilir (dinamik olarak yazılmış dillerde bile listeleri kullanabilirsiniz). Tote'ler diğer dillerde de desteklenmektedir:

f :: Int -> (Int, Int)
f x = (x - 1, x + 1)

// Even C++ have tuples - see Boost.Graph for use
std::pair<int, int> f(int x) {
  return std::make_pair(x - 1, x + 1);
}

Yukarıdaki örnekte, f2 inç döndüren bir işlevdir.

Benzer şekilde, ML, Haskell, F #, vb., Veri yapılarını da döndürebilir (işaretçiler çoğu dil için çok düşük düzeydedir). Böyle bir kısıtlama ile modern bir GP dili duymadım:

data MyValue = MyValue Int Int

g :: Int -> MyValue
g x = MyValue (x - 1, x + 1)

Son olarak, outfonksiyonel dillerde bile parametreler taklit edilebilir IORef. Çoğu dilde dışarı değişkenler için yerel destek olmamanın birkaç nedeni vardır:

  • Net olmayan anlambilim : Aşağıdaki işlev 0 mı yoksa 1 mi yazdırıyor? 0 basacak dilleri ve 1 basacak dilleri biliyorum. Her ikisinin de yararları vardır (hem performans açısından hem de programcının zihinsel modelini eşleştirmenin yanı sıra):

    int x;
    
    int f(out int y) {
      x = 0;
      y = 1;
      printf("%d\n", x);
    }
    f(out x);
    
  • Lokalize olmayan etkiler : Yukarıdaki örnekte olduğu gibi, uzun bir zincire sahip olabileceğinizi ve en içteki fonksiyonun küresel durumu etkilediğini görebilirsiniz. Genel olarak, işlevin gereksinimlerinin ne olduğu ve değişimin yasal olup olmadığına dair gerekçeyi zorlaştırır. Modern paradigmaların çoğunun ya etkileri yerelleştirmeye (OOP'de kapsülleme) ya da yan etkileri ortadan kaldırmaya çalıştığı göz önüne alındığında, bu paradigmalar ile çelişir.

  • Yedekli Olmak : Eğer perdelere sahipseniz, outparametrelerin işlevselliğinin% 99'una ve deyimsel kullanımın% 100'üne sahipsiniz . Karışıma işaretçiler eklerseniz, kalan% 1'i karşılarsınız.

Bir dize, sınıf veya outparametre kullanarak birden fazla değer döndüremeyen bir dilin adlandırılmasında sorun yaşıyorum (ve çoğu durumda bu yöntemlerden 2 veya daha fazlasına izin verilir).


+1 İşlevsel dillerin bunu zarif ve ağrısız bir şekilde ele almasından bahsetmek için.
Andres F.

1
Teknik olarak, hala hala tek bir değer döndürüyorsunuz: D (Sadece bu tek değerin birden çok değere ayrıştırılması önemsizdir).
Thomas Eding

1
Gerçek "dışarı" anlambilimine sahip bir parametrenin, bir yöntem normal olarak çıktığında hedefe kopyalanan geçici bir derleyici gibi davranması gerektiğini söyleyebilirim; "inout" semantik değişkeni olan biri, girilen değişkenden girişte yüklenen ve çıkışa geri yazılan geçici bir derleyici gibi davranmalıdır; "ref" semantiğine sahip bir takma ad gibi davranmalıdır. C # nın "out" parametreleri gerçekten "ref" parametreleridir ve böyle davranırlar.
supercat

1
"Geçici çözüm" parçası da ücretsiz gelmiyor. Optimizasyon fırsatlarını engeller. CPU kayıtlarında N dönüş değerlerinin döndürülmesine izin veren bir ABI varsa, derleyici, bir tuple örneği oluşturmak ve onu oluşturmak yerine gerçekten optimize edilebilir.
BitTickler

1
@BitTickler, ABI'yi kontrol ediyorsanız, kayıtların geçirdiği ilk n yapı alanını döndürmeyi engelleyen hiçbir şey yoktur.
Maciej Piechotka

6

Bunun gibi ifadeler yüzünden olduğunu düşünüyorum (a + b[i]) * c.

İfadeler "tekil" değerlerden oluşur. Böylece tekil bir değer döndüren bir fonksiyon, yukarıda gösterilen dört değişkenden herhangi birinin yerine, bir ifadede doğrudan kullanılabilir. Çok çıkışlı bir işlev bir ifadede en azından biraz sakardır.

Şahsen bunun tekil bir dönüş değeri için özel olan şey olduğunu hissediyorum . Bir ifadede kullanmak istediğiniz birden çok dönüş değerinden hangisini belirtmek için sözdizimi ekleyerek bunun üstesinden gelinebilirsiniz, ancak eski ve eski matematiksel gösterime göre daha hantal ve herkes için aşinadır.


4

Sözdizimini biraz zorlaştırıyor, ancak uygulama düzeyinde izin vermemek için iyi bir neden yok. Diğer yanıtların bazılarına aykırı olarak, varsa, birden fazla değerin döndürülmesi, daha net ve daha verimli kodlara yol açar. Ne kadar sıklıkla bir X ve Y ya da "başarılı" bir boole ve faydalı bir değer döndürmek istediğimi hesaplayamıyorum .


3
Birden fazla iadenin daha net ve / veya daha verimli kod sağladığı bir örnek verebilir misiniz?
Steven Evers

3
Örneğin, C ++ COM programlamada, birçok fonksiyonun bir [out]parametresi vardır, fakat hemen hemen hepsi bir HRESULT(hata kodu) döndürür . Orada sadece bir çift almak oldukça pratik olurdu. Python gibi tekiller için iyi desteği olan dillerde, gördüğüm birçok kodda kullanılır.
Felix Dombek

Bazı dillerde, X ve Y koordinatına sahip bir vektör döndürürsünüz ve faydalı herhangi bir değeri geri döndürmek, başarısızlık için kullanılan, muhtemelen bu faydalı değeri taşıyan istisnalar dışında "başarı" olarak sayılır.
doppelgreener

3
Bilgiyi, dönüş değerine açık olmayan şekillerde kodlama ile sonuçlanır - yani; negatif değerler hata kodları, pozitif değerler sonuçtur. Yuk. Bir karma tablosuna erişerek, öğenin bulunup bulunmadığını belirtmek ve öğeyi döndürmek her zaman karışıktır.
ddyer

Matlab @SteveEvers sortfonksiyonu normal bir dizi sıralar: sorted_array = sort(array). Bazen ben de karşılık gelen endeks gerekir: [sorted_array, indices] = sort(array). Bazen sadece endeksleri istiyorum: [~, indices]= sort (array) . The function sort` aslında kaç tane çıkış argümanına ihtiyaç duyulduğunu söyleyebilir, bu nedenle 1'e kıyasla 2 çıkış için ek çalışma gerekiyorsa, bu çıkışları yalnızca gerektiğinde hesaplayabilir.
gerrit

2

İşlevlerin desteklendiği çoğu dilde, bu türden bir değişkenin kullanılabileceği her yerde işlev çağrısı kullanabilirsiniz: -

x = n + sqrt(y);

İşlev birden fazla değer döndürürse, bu çalışmaz. Dinamik olarak yazılan python gibi diller bunu yapmanıza izin verir, ancak çoğu durumda denklemin ortasındaki bir tuple ile ilgili bir şeyler yapamadıkça çalışma zamanı hatası verir.


5
Sadece uygunsuz işlevleri kullanmayın. Bu, hiçbir değer döndürmeyen ya da sayısal olmayan değerleri döndüren işlevlerin yarattığı "sorun" dan farklı değildir.
ddyer

3
Kullandığım birden fazla dönüş değeri sunan dillerde (örneğin, SciLab), ilk dönüş değeri ayrıcalıklıdır ve yalnızca bir değerin gerekli olduğu durumlarda kullanılacaktır. Yani orada gerçek bir sorun yok.
Photon,

Ve olmasalar bile, Python'un foo()[0]
çekiş paketini açmak gibi,

Tam olarak, bir işlev 2 değer döndürürse, dönüş türü tek bir değer değil 2 değerdir. Programlama dili aklınızı okumamalıdır.
Mark E. Haase,

1

Sadece Harvey'in cevabı üzerine inşa etmek istiyorum. Başlangıçta bu soruyu bir haber teknolojisi sitesinde (arstechnica) buldum ve bu sorunun özüne gerçekten cevap verdiğini ve diğer tüm cevaplardan (Harvey's hariç) yoksun olduğuna inandığım şaşırtıcı bir açıklama buldum:

İşlevlerden tek dönüşün kökeni, makine kodundadır. Makine kodu seviyesinde, bir fonksiyon A (akümülatör) kaydında bir değer döndürebilir. Diğer tüm iade değerleri yığında olacaktır.

İki dönüş değerini destekleyen dil, onu döndüren makine kodu olarak derlenecek ve ikinciyi yığına koyacaktır. Başka bir deyişle, ikinci dönüş değeri yine de bir çıkış parametresi olarak bitecektir.

Atamanın neden her seferinde bir değişken olduğunu sormak gibi. Mesela a, b = 1, 2 izin verilen bir dile sahip olabilirsiniz. Ancak makine kodu seviyesinde a = 1 ve ardından b = 2 olur.

Programlama dili yapılarının, kod derlenirken ve çalıştırılırken gerçekte ne olacağına dair bir miktar belirsizliğe sahip olmasının gerekçesi vardır.


C gibi düşük seviyeli diller birinci sınıf bir özellik olarak çoklu dönüş değerlerini destekliyorsa, C çağırma kuralları birden çok dönüş değeri kaydı içerir, aynı şekilde x86- 64 Sistem V ABI. (Aslında x86-64 SysV, RDX: RAX kayıt çiftine paketlenmiş 16 bayta kadar yapı döndürür. 64 bitten daha
Peter Cordes

Belli kongre RAX, sonra argüman geçen regs olacaktır. (RDI, RSI, RDX, RCX, R8, R9). Veya Windows x64 sözleşmesinde, RCX, RDX, R8, R9. Ancak, C doğal olarak çoklu dönüş değerlerine sahip olmadığından, C ABI / arama kuralları yalnızca geniş tamsayılar ve bazı yapılar için çoklu dönüş kayıtları belirtir. ARM® Mimarisi için Prosedür Çağrısı Standardı'na bakın : Derleyicinin ARM'e 2 dönüş değeri almak için verimli bir asm yapmasını sağlamak için geniş bir int kullanma örneği için 2 ayrı, ancak ilgili dönüş değerleri .
Peter Cordes

-1

Matematik ile başladı. FORTRAN, "Formula Çeviri" olarak adlandırılan ilk derleyiciydi. FORTRAN fizik / matematik / mühendisliğe yöneldi ve ona yöneldi.

COBOL, neredeyse eski, açık bir geri dönüş değerine sahip değildi; Ancak alt yordamlar vardı. O zamandan beri çoğunlukla atalet oldu.

Örneğin Go , çoklu dönüş değerlerine sahiptir ve sonuç "out" parametrelerini kullanmaktan daha temiz ve daha az belirsizdir. Biraz kullandıktan sonra çok doğal ve verimlidir. Tüm yeni diller için birden fazla dönüş değeri düşünülmesini öneririm. Belki eski diller için de.


4
bu soruyu cevaplamıyor
gnat

@ gnat benim için cevap veriyor. OTOH'un bunu anlamak için bir geçmişe ihtiyacı var ve bu kişi muhtemelen soruyu sormayacak ...
Netch

@ Netch bir "FORTRAN ... ilk derleyici oldu" gibi ifadelerin toplam bir karmaşa olduğunu anlamak için çok fazla bilgiye ihtiyaç duymaz . Yanlış bile değil. Aynen bu "cevabın" geri kalanı gibi
gnat

link derleyicilerde daha önce denemeler yapıldığını, ancak “IBM’de John Backus’un önderlik ettiği FORTRAN ekibinin 1957’de ilk tam derleyiciyi tanıttığı kabul edildi” diyor. Sorulan soru neden sadece biriydi? Söylediğim gibi. Çoğunlukla matematik ve ataletti. "İşlev" teriminin matematiksel tanımı tam bir sonuç değeri gerektirir. Yani tanıdık bir formdu.
RickyS

-2

Muhtemelen, işlev çağrılarının işlemcinin makine talimatlarında nasıl yapıldığının mirası ve tüm programlama dillerinin makine kodundan geldiği gerçeğiyle daha fazla ilgisi vardır: örneğin, C -> Assembly -> Makine.

İşlemciler İşlev Çağrısını Nasıl Gerçekleştirir?

İlk programlar makine kodu ve daha sonra montaj ile yazılmıştır. İşlemciler tarafından desteklenen işlev, mevcut tüm kayıt defterlerinin bir kopyasını yığına iterek çağırır. İşlevden geri dönmek kaydedilmiş kayıt kümesini yığından açar. Dönen fonksiyonun bir değer getirmesini sağlamak için genellikle bir sicile dokunulmazdı.

Şimdi, neden işlemcilerin bu şekilde tasarlandığına gelince ... bu büyük olasılıkla kaynak kısıtlamaları sorunuydu.

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.