Bir işlevde açıkça dönüşü çağırıp çağırmamak


200

Bir süre önce ben azarladı var açıkça çağıran bir kullanıcı tavsiye R çekirdek ekibi (inanıyorum) dan Simon Urbanek tarafından return(onun yorum olsa silindi) bir fonksiyonu sonunda:

foo = function() {
  return(value)
}

bunun yerine şunları önerdi:

foo = function() {
  value
}

Muhtemelen böyle bir durumda gereklidir:

foo = function() {
 if(a) {
   return(a)
 } else {
   return(b)
 }
}

Onun yorumu, returnkesinlikle gerekmedikçe neden aramamanın iyi bir şey olduğuna ışık tuttu , ancak bu silindi.

Sorum şu: Neden returndaha hızlı veya daha iyi ve neden tercih edilmiyor ?


12
returnson örnekte bile gereksizdir. Kaldırmak returnbiraz daha hızlı olabilir, ancak bence bunun R'nin işlevsel bir programlama dili olduğu söyleniyor.
kohske

4
@kohske Yorumunuzu neden daha hızlı olduğu ve bunun işlevsel bir programlama dili olmasıyla nasıl ilişkili olduğu hakkında daha fazla ayrıntı da dahil olmak üzere bir cevaba genişletebilir misiniz?
Paul Hiemstra

2
returnyerel olmayan atlamayı indükler ve FP için açık yerel olmayan atlama olağandışıdır. Aslında, örneğin, şema yoktur return. Cevaplarımın cevap olarak çok kısa (ve belki de yanlış) olduğunu düşünüyorum.
kohske

2
F # yok return, break, continuesıkıcı bazen ya, hangi.
colinfang

Yanıtlar:


129

Soru şuydu: Neden (açıkça) geri dönüşü daha hızlı veya daha iyi ve dolayısıyla tercih edilebilir değil?

R belgelerinde böyle bir varsayımı yapan herhangi bir ifade yoktur.
Ana sayfa? 'İşlevi' diyor:

function( arglist ) expr
return(value)

Dönüş çağrısı yapmadan daha hızlı mı?

Hem function()ve return()ilkel işlevlerdir ve function()kendisi işlevi dahil etmeden bile son değerlendirilen değeri döndürür return().

Arayan return()olarak .Primitive('return')bağımsız değişken olarak bu son değere sahip aynı işi yapmak ama daha bir çağrıyı ihtiyacı olacaktır. Böylece bu (genellikle) gereksiz .Primitive('return')çağrı ek kaynaklar çekebilir. Bununla birlikte basit ölçüm, ortaya çıkan farkın çok küçük olduğunu ve bu nedenle açık dönüşü kullanmamanın nedeni olamayacağını gösterir. Bu şekilde seçilen verilerden aşağıdaki grafik oluşturulur:

bench_nor2 <- function(x,repeats) { system.time(rep(
# without explicit return
(function(x) vector(length=x,mode="numeric"))(x)
,repeats)) }

bench_ret2 <- function(x,repeats) { system.time(rep(
# with explicit return
(function(x) return(vector(length=x,mode="numeric")))(x)
,repeats)) }

maxlen <- 1000
reps <- 10000
along <- seq(from=1,to=maxlen,by=5)
ret <- sapply(along,FUN=bench_ret2,repeats=reps)
nor <- sapply(along,FUN=bench_nor2,repeats=reps)
res <- data.frame(N=along,ELAPSED_RET=ret["elapsed",],ELAPSED_NOR=nor["elapsed",])

# res object is then visualized
# R version 2.15

İşlev geçen zaman karşılaştırması

Yukarıdaki resim platformunuzda biraz farklı olabilir. Ölçülen verilere dayanarak, döndürülen nesnenin boyutu herhangi bir fark yaratmaz, tekrar sayısı (ölçeklendirilse bile) sadece çok küçük bir fark yaratır, bu da gerçek veriler ve gerçek algoritma ile gerçek kelimede sayılamaz veya komut dosyası daha hızlı çalışır.

Dönüş çağrısı yapmadan daha mı iyi?

Return rutin sona ermesi, fonksiyonun dışına atlaması ve değer döndürmesi gereken kodun "yaprakları" nı açıkça tasarlamak için iyi bir araçtır.

# here without calling .Primitive('return')
> (function() {10;20;30;40})()
[1] 40
# here with .Primitive('return')
> (function() {10;20;30;40;return(40)})()
[1] 40
# here return terminates flow
> (function() {10;20;return();30;40})()
NULL
> (function() {10;20;return(25);30;40})()
[1] 25
> 

Hangi stilin kullandığını programcının strateji ve programlama stiline bağlıdır, gerekli olmadığı için return () kullanamaz.

R çekirdek programcıları her iki yaklaşımı da kullanır. 'base' fonksiyonlarının kaynaklarında bulmak mümkün olduğu için açıkça return () ile birlikte ve return () olmadan.

Birçok kez işlevi döndürmek için NULL döndüren yalnızca return () kullanılır (bağımsız değişken yok).

Standart kullanıcı veya R'yi kullanan analistin gerçek farkı göremediği daha iyi olup olmadığı net değildir.

Benim düşüncem şu soru olmalıdır: R uygulamasından gelen net getiriyi kullanma tehlikesi var mı?

Ya da, belki daha iyi kullanıcı yazma fonksiyonu kodu daima sormalısınız: in etkisi nedir değil açık getiri (veya kod şube son yaprağı olarak iade edilecek nesneyi yerleştirerek) işlevi kodunda?


4
Çok iyi bir cevap için teşekkürler. Kullanmanın herhangi bir tehlikesi olmadığına inanıyorum returnve programlayıcının kullanılıp kullanılmayacağı tercihine bağlı.
Paul Hiemstra

38
Hızı returngerçekten endişelenmeniz gereken son şey.
hadley

2
Bence bu kötü bir cevap. Sebepler, gereksiz returnişlev çağrılarının kullanılmasının değeri konusunda temel bir anlaşmazlıktan kaynaklanmaktadır . Sormanız gereken soru, sonunda önerdiğiniz soru değil. Bunun yerine, bu kadar: “neden olmalıdır Ben gereksiz kullanmak return? Hangi fayda sağlıyor? ” Anlaşıldığı üzere, cevap “fazla değil”, hatta “hiçbiri” değil. Cevabınız bunu takdir edemiyor.
Konrad Rudolph

@KonradRudolph ... Paul'un aslında sorduğu şeyi tekrarladın (açık dönüş neden kötüdür). Ve ben de (herkes için doğru olanı) yanıtı bilmek istiyorum :). Bu sitenin kullanıcıları için açıklamanızı sağlamayı düşünüyor musunuz?
Petr Matousu

1
@Dason Başka bir yerde, bu argümanın neden bu bağlamda kusurlu olduğunu açıklayan bir Reddit yazısı bağladım. Ne yazık ki yorum her seferinde otomatik olarak kaldırılıyor gibi görünüyor. Kısa sürüm, returnbir kod parçasının yanında “x'i 1 arttır” yazan açık bir yorum gibidir x = x + 2. Başka bir deyişle, açıklığı (a) tamamen ilgisizdir ve (b) yanlış bilgileri iletir . Çünkü returnR'nin anlambilimi, tamamen “bu işlevi iptal et” dir. O mu değil aynı anlama returndiğer dillerde.
Konrad Rudolph

103

Herkes bunu kabul ederse

  1. return bir işlev gövdesinin sonunda gerekli değildir
  2. kullanmamak returnmarjinal olarak daha hızlıdır (@ Alan'ın testine göre, 5.1 mikrosaniye ve 5.1'e göre)

returnbir fonksiyonun sonunda kullanmayı bırakmalı mıyız ? Kesinlikle yapmayacağım ve nedenini açıklamak istiyorum. Başkalarının fikrimi paylaşıp paylaşmadığını duymayı umuyorum. Ve OP'ye doğrudan bir cevap değil, daha çok uzun bir öznel yorum gibi olursa özür dilerim.

Kullanmamanın temel problemi return, Paul'un işaret ettiği gibi, bir fonksiyonun vücudunda ihtiyacınız olabilecek başka yerler olmasıdır. Ve returnişlevinizin ortasında bir yerde kullanmak zorunda kalırsanız , neden tüm returnifadeleri açıklaştırmıyorsunuz? Tutarsız olmaktan nefret ediyorum. Ayrıca kod daha iyi okuyor düşünüyorum; biri fonksiyonu tarayabilir ve tüm çıkış noktalarını ve değerleri kolayca görebilir.

Paul bu örneği kullandı:

foo = function() {
 if(a) {
   return(a)
 } else {
   return(b)
 }
}

Ne yazık ki, kolayca şöyle yeniden yazılabileceği belirtilebilir:

foo = function() {
 if(a) {
   output <- a
 } else {
   output <- b
 }
output
}

İkinci sürüm, işlev başına bir dönüş ifadesini savunan bazı programlama kodlama standartlarına bile uygundur. Bence daha iyi bir örnek olabilirdi:

bar <- function() {
   while (a) {
      do_stuff
      for (b) {
         do_stuff
         if (c) return(1)
         for (d) {
            do_stuff
            if (e) return(2)
         }
      }
   }
   return(3)
}

Bu, tek bir dönüş ifadesi kullanarak yeniden yazmak çok daha zor olacaktır: breakbunları çoğaltmak için birden fazla s ve karmaşık bir boole değişken sistemine ihtiyaç duyacaktır . Bütün bunlar tek dönüş kuralının R ile iyi oynadığını söylemek için. Eğer returnfonksiyonunuzun vücudunun bazı yerlerinde kullanmanız gerekiyorsa , neden tutarlı olmuyorsunuz ve her yerde kullanmıyorsunuz?

Hız argümanının geçerli olduğunu düşünmüyorum. Aslında bir şey yapan işlevlere bakmaya başladığınızda 0,8 mikrosaniye fark yoktur. Görebildiğim son şey daha az yazıyor ama hey, tembel değilim.


7
+1, return@flodel'in gösterdiği gibi, bazı durumlarda ifadeye açık bir ihtiyaç vardır . Alternatif olarak, bir return ifadesinin en iyi çıkarıldığı durumlar vardır, örneğin çok sayıda ve çok sayıda küçük işlev çağrısı. Vakaların% 95'inde, birinin kullanılıp kullanılmadığı gerçekten önemli returndeğil ve tercih edilir. Ne demek istediğini daha açık, böylece daha okunabilir olduğu gibi dönüş kullanmayı seviyorum. Belki bu tartışma için akin edilir <-vs =?
Paul Hiemstra

7
Bu, R'yi zorunlu bir programlama dili olarak görüyor, öyle değil: fonksiyonel bir programlama dili. Fonksiyonel programlama basitçe farklı şekilde çalışır ve returnbir değeri döndürmek için kullanmak saçmadır, if (x == TRUE)bunun yerine yazma yerine if (x).
Konrad Rudolph

4
Ayrıca yeniden yazmak fooolarak foo <- function(x) if (a) a else b(gerekli olduğu satır aralıkları olan). Açık dönüş veya ara değere gerek yoktur.
hadley

26

Bu enteresan bir tartışma. @ Flodel'in örneğinin mükemmel olduğunu düşünüyorum. Bununla birlikte, işlevsel bir kodlama stili yerine returnbir zorunluluk kullandığınızda mantıklı olan benim açımdan (ve bir yorumda @koshke'den bahsettiğini) açıkladığını düşünüyorum .

Bu noktayı suçlamak değil, ama şöyle yazmıştım foo:

foo = function() ifelse(a,a,b)

İşlevsel bir stil, değerini depolamak gibi durum değişikliklerini önler output. Bu tarzda, returnyer dışında; foodaha çok matematiksel bir işleve benziyor.

@Flodel ile aynı fikirdeyim: Karmaşık bir boolean değişken sistemi kullanmak bar, daha az net ve anlamsız olurdu return. Ne yapar barbu kadar müsait returntabloların bir zorunluluk tarzda yazılmış olmasıdır. Gerçekten de, boole değişkenleri fonksiyonel bir tarzda kaçınılan "durum" değişikliklerini temsil eder.

barİşlevsel tarzda yeniden yazmak gerçekten zordur , çünkü sadece sahte koddur, ancak fikir şu şekildedir:

e_func <- function() do_stuff
d_func <- function() ifelse(any(sapply(seq(d),e_func)),2,3)
b_func <- function() {
  do_stuff
  ifelse(c,1,sapply(seq(b),d_func))
}

bar <- function () {
   do_stuff
   sapply(seq(a),b_func) # Not exactly correct, but illustrates the idea.
}

whileO devlet değişiklikleri ile kontrol edilir, çünkü döngü, yeniden yazmak en zor olurdu a.

Bir çağrının neden olduğu hız kaybı returnihmal edilebilir, ancak returnişlevsel bir tarzda kaçınarak ve yeniden yazarak elde edilen verimlilik genellikle çok yüksektir. Yeni kullanıcılara kullanmayı bırakmalarını söylemek returnmuhtemelen yardımcı olmaz, ancak onları işlevsel bir stile yönlendirmek işe yarayacaktır.


@Paul returnzorunlu bir tarzda gereklidir, çünkü genellikle bir döngüdeki farklı noktalardan işlevden çıkmak istersiniz. İşlevsel bir tarzda döngüler kullanılmaz ve bu nedenle gerekmez return. Tamamen işlevsel bir tarzda, son çağrı neredeyse her zaman istenen dönüş değeridir.

Python'da işlevler bir returnifade gerektirir . Ancak, işlevinizi işlevsel bir tarzda programladıysanız, büyük olasılıkla yalnızca bir returndeyiminiz olur: işlevinizin sonunda.

Başka bir StackOverflow gönderisinden bir örnek kullanarak, TRUEbelirli bir değerdeki tüm değerlerin xtek bir uzunluğu varsa döndürülen bir işlev istediğimizi varsayalım . İki stil kullanabiliriz:

# Procedural / Imperative
allOdd = function(x) {
  for (i in x) if (length(i) %% 2 == 0) return (FALSE)
  return (TRUE)
}

# Functional
allOdd = function(x) 
  all(length(x) %% 2 == 1)

İşlevsel bir tarzda, döndürülecek değer doğal olarak işlevin uçlarına düşer. Yine, daha çok matematiksel bir fonksiyona benziyor.

@Genişler Ana hatlarıyla verilen uyarılar ?ifelsekesinlikle ilginç, ancak işlevin kullanımını caydırmaya çalıştıklarını sanmıyorum. Aslında, ifelsefonksiyonları otomatik olarak vektörleme avantajına sahiptir. Örneğin, biraz değiştirilmiş bir sürümünü düşünün foo:

foo = function(a) { # Note that it now has an argument
 if(a) {
   return(a)
 } else {
   return(b)
 }
}

Bu işlev length(a)1 olduğunda iyi çalışır . Ancak foo, birifelse

foo = function (a) ifelse(a,a,b)

Şimdi fooherhangi bir uzunlukta çalışır a. Aslında, abir matris olduğunda bile işe yarayacaktır . Değeri, testvektörleştirmeye yardımcı olan bir özellikle aynı şekilde döndürmek , sorun değil.


returnİşlevsel bir programlama tarzına neden uymadığı net değil. Biri zorunlu veya işlevsel olarak programlama yapıyor olsun, bir aşamada bir fonksiyonun veya altyordamın bir şey döndürmesi gerekir. Örneğin, python'da işlevsel programlama hala bir returnifade gerektirir . Bu konuda daha fazla ayrıntı verebilir misiniz?
Paul Hiemstra

Bu durumda, kullanmak ifelse(a,a,b)benim bir evcil hayvanımdır. Görünüşe göre her satır ?ifelsebağırıyor, "yerine beni kullanma if (a) {a} else b". ör. "... test", " ile aynı şekle sahip bir değer döndürür yesveya noçok kısaysa, öğeleri geri dönüştürülür.", "sonucun modu, sonucun testsınıf niteliği olan", " değerine bağlı olabilir adlı kullanıcıdan alınan ve "testyesno
GSee

İkinci bakışta foopek mantıklı değil; her zaman TRUE veya b. Kullanıldığında ifelse1 veya birkaç DOĞRU ve / veya 1 veya birkaç bsaniye döndürülür . Başlangıçta, işlevin amacının "bazı ifadeler DOĞRU ise, bir şey döndür, aksi halde başka bir şey döndür" demek olduğunu düşündüm. Bunun vektörleştirilmesi gerektiğini düşünmüyorum, çünkü o zaman "DOĞRU olan bir nesnenin öğelerini döndürür ve DOĞRU olmayan tüm öğeler için geri döner b.
GSee

22

Görünüşe göre return()daha hızlı olmadan ...

library(rbenchmark)
x <- 1
foo <- function(value) {
  return(value)
}
fuu <- function(value) {
  value
}
benchmark(foo(x),fuu(x),replications=1e7)
    test replications elapsed relative user.self sys.self user.child sys.child
1 foo(x)     10000000   51.36 1.185322     51.11     0.11          0         0
2 fuu(x)     10000000   43.33 1.000000     42.97     0.05          0         0

____ DÜZENLE__ _ __ _ __ _ __ _ __ _ ___

Başkalarına benchmark ( benchmark(fuu(x),foo(x),replications=1e7)) ile devam ediyorum ve sonuç tersine döndü ... Bir sunucuda deneyeceğim.


Bu farkın neden ortaya çıktığı hakkında yorum yapabilir misiniz?
Paul Hiemstra

4
@PaulHiemstra Petr'ın Cevabı, bunun ana nedenlerinden birini kapsar; kullanırken iki çağrı return(), eğer kullanmazsanız bir çağrı . Bir fonksiyonun sonunda function()son değerini döndürdüğü için tamamen gereksizdir . Bunu sadece dahili olarak fazla bir şey yapılmadığı bir fonksiyonun birçok tekrarında fark edersiniz, böylece maliyeti return()toplam hesaplama fonksiyonunun büyük bir parçası haline gelir.
Gavin Simpson

13

Sonuna açıkça 'return' koymama sorunu, yöntemin sonuna ek ifadeler eklenirse, aniden dönüş değerinin yanlış olmasıdır:

foo <- function() {
    dosomething()
}

Bu, değerini döndürür dosomething().

Şimdi ertesi gün gelip yeni bir satır ekliyoruz:

foo <- function() {
    dosomething()
    dosomething2()
}

Kodumuzun değerini döndürmesini istedik dosomething(), ancak bunun yerine artık yok.

Açık bir dönüşle, bu gerçekten belirgin hale gelir:

foo <- function() {
    return( dosomething() )
    dosomething2()
}

Bu kod hakkında garip bir şey olduğunu görebilir ve düzeltebiliriz:

foo <- function() {
    dosomething2()
    return( dosomething() )
}

1
Evet, aslında hata ayıklama sırasında açık bir return () yararlı olduğunu buluyorum; kod temizlendikten sonra, ihtiyacı daha az zorlayıcı ve ben sahip olmama zarafetini tercih ediyorum ...
PatrickT

Ama bu aslında gerçek kodda bir sorun değil, tamamen teorik. Bundan muzdarip olabilecek kodun çok daha büyük bir sorunu vardır: basit, eklemelerin kırılması çok açık olmayan, belirsiz, kırılgan bir kod akışı.
Konrad Rudolph

@KonradRudolph Sanırım üzerinde No-True Scotsman yapıyorsunuz ;-) "Eğer kodunuzda bir sorun varsa, kötü bir programcısınız!". Gerçekten katılmıyorum. Bence her satırı ezbere bildiğiniz küçük kod parçaları üzerinde kısa yollar alarak kaçabilirken, kodunuz büyüdükçe bu sizi ısırmaya geri dönecektir.
Hugh Perkins

2
@HughPerkins Gerçek bir İskoçyalı değil ; daha ziyade, onlarca yazılım mühendisliği en iyi uygulamasıyla desteklenen kod karmaşıklığı hakkında ampirik bir gözlemdir: bireysel işlevleri kısa tutun ve kod akışını açık tutun. Ve atlamak returnbir kısayol değildir , fonksiyonel programlamada uygun stildir. Gereksiz returnfonksiyon çağrılarının kullanılması kargo kült programlamanın bir örneğidir .
Konrad Rudolph

Şey ... Bunun, ifadenizden sonra bir şey eklemenizi nasıl engellediğini returnve yürütülmeyeceğini fark etmediğimi görmüyorum. Geri dönmek istediğiniz değerden sonra bir yorum da ekleyebilirsinizdosomething() # this is my return value, don't add anything after it unless you know goddam well what you are doing
lebatsnok

10

Sorum şu: Neden returndaha hızlı aramıyor?

Daha hızlıdır, çünkü returnR'deki (ilkel) bir işlevdir, yani kodda kullanmanın bir işlev çağrısının maliyetine neden olduğu anlamına gelir. Bunu return, bir anahtar kelime olan, ancak bir işlev çağrısı olmayan diğer programlama dillerinin çoğu ile karşılaştırın : herhangi bir çalışma zamanı kodu yürütülmesine dönüşmez.

Bununla birlikte, bu şekilde ilkel bir fonksiyon çağırmak R'de oldukça hızlıdır ve çağrı returnminik bir ek yüke neden olur. Bu ihmal edilme iddiası değil return.

ya da daha iyisi ve dolayısıyla tercih edilebilir mi?

Neden yok çünkü üzere kullanabilirsiniz.

Çünkü gereksizdir ve faydalı fazlalık sağlamaz .

Açıklamak gerekirse: fazlalık olabilir bazen yararlı olabilir . Ancak işten çıkarmanın çoğu bu tür değildir. Bunun yerine, bilgi eklemeden görsel karmaşayı ekleyen türdür : bir dolgu kelimesinin veya grafik önemsizinin programlama eşdeğeri ).

Evrensel olarak kötü fazlalık olarak kabul edilen açıklayıcı bir yorum örneğini göz önünde bulundurun, çünkü yorum sadece kodun zaten ifade ettiği şeyi açıklar:

# Add one to the result
result = x + 1

returnR'de kullanmak aynı kategoriye girer, çünkü R fonksiyonel bir programlama dilidir ve R'de her fonksiyon çağrısının bir değeri vardır . Bu bir temel R. malı Ve (her işlev çağrısı dahil) her ifade bir değere sahip olduğunu perspektifinden R kodunu gördüğünüzde, soru şuna dönüşür: “neden gerektiğini Kullandığım return?” Varsayılan bir neden kullanmak olmadığından, olumlu bir neden olması gerekir.

Böyle olumlu bir neden, bir görev cümlesinde , örneğin bir işlevden erken çıkmanın sinyalidir :

f = function (a, b) {
    if (! precondition(a)) return() # same as `return(NULL)`!
    calculation(b)
}

Bu, geçerli, gereksiz olmayan bir kullanımdır return. Bununla birlikte, bu tür koruma maddeleri R'de diğer dillerle karşılaştırıldığında nadirdir ve her ifadenin bir değeri olduğundan, düzenli bir ifşey gerektirmez return:

sign = function (num) {
    if (num > 0) {
        1
    } else if (num < 0) {
        -1
    } else {
        0
    }
}

Hatta fböyle yeniden yazabiliriz :

f = function (a, b) {
    if (precondition(a)) calculation(b)
}

… İle if (cond) expraynı nerede if (cond) expr else NULL.

Son olarak, üç yaygın itirazı öngörmek istiyorum:

  1. Bazı insanlar kullanmanın returnnetlik kattığını savunur , çünkü “bu işlev bir değer döndürür” sinyalini verir. Ancak yukarıda açıklandığı gibi, her fonksiyon R'de bir şey döndürür return. Bir değer döndürmenin bir göstergesi olarak düşünmek sadece gereksiz değildir, aktif olarak yanıltıcıdır .

  2. İlgili olarak, Python Zen'in her zaman takip edilmesi gereken muhteşem bir rehber vardır:

    Açık, örtük olmaktan iyidir.

    Gereksizliği düşürmek returnbunu ihlal etmez mi? İşlevsel bir dilde bir işlevin dönüş değeri her zaman açık olduğundan: bu onun son ifadesidir. Bu yine açıklık ve fazlalık ile ilgili aynı argüman .

    İşareti fonksiyonları: Eğer belirginliği istiyorsanız Aslında, kuralın istisnası vurgulamak için kullanabilirsiniz yok sadece kendi yan etkileri (örneğin için denir anlamlı bir değere dönmek cat). Dışında R daha iyi bir işaret içermektedir returnBu durum için: invisible. Mesela ben yazardım

    save_results = function (results, file) {
        # … code that writes the results to a file …
        invisible()
    }
  3. Peki ya uzun fonksiyonlar? Nelerin iade edildiğini takip etmek kolay olmayacak mı?

    İki cevap: Birincisi, tam olarak değil. Kural açıktır: bir fonksiyonun son ifadesi onun değeridir. İzlenecek bir şey yok.

    Ancak daha da önemlisi, uzun işlevlerdeki sorun açık returnbelirteçlerin olmaması değildir . Bu var fonksiyonunun uzunluğu . Uzun fonksiyonlar neredeyse (?) Her zaman tek sorumluluk ilkesini ihlal eder ve yapmadıkları zaman bile okunabilirlik için parçalanmaktan faydalanırlar.


Belki eklemeliyim ki bazı insanlar returnbunu diğer dillere daha benzer hale getirmek için kullanmayı savunuyorlar . Ancak bu kötü bir argüman: diğer fonksiyonel programlama dilleri de kullanma eğiliminde değil return. Bu tek şart kullanmayın her ifade bir değere sahiptir dilleri,.
Konrad Rudolph

Bu returnsoruya, kullanımın açıkça desteklediğini ve cevabınızı tam eleştiriyle okuduğu görüşüyle ​​geldim . Cevabınız beni bu görüş üzerinde düşünmeye itti. Ben gerek kullanmayı düşünüyorum return, daha iyi bir ihtiyaca bağlıdır (en azından benim kendi durumunda) açıkça daha sonraki bir zaman noktasında benim fonksiyonları revize edebilecektir. Fonksiyonlarımın çok karmaşık olabileceği düşüncesiyle, artık programlama tarzımı geliştirmek için bir hedefin, açıklık olmadan a korumak için kodları yapılandırmak için çabalamak olduğunu görebiliyorum return. Bu düşünceler ve içgörü için teşekkürler !!
Kasper Thystrup Karstensen

6

returnBir hile olarak düşünüyorum . Genel bir kural olarak, bir işlevde değerlendirilen son ifadenin değeri işlevin değeri olur - ve bu genel örüntü birçok yerde bulunur. Aşağıdakilerin tümü 3 olarak değerlendirilir:

local({
1
2
3
})

eval(expression({
1
2
3
}))

(function() {
1
2
3
})()

Ne returnyapar gerçekten değil dönen (bu birlikte veya onsuz yapılır) bir değer ancak düzensiz bir şekilde fonksiyon "patlak". Bu anlamda, R'deki GOTO ifadesinin en yakın eşdeğeridir (ayrıca break ve next vardır). returnÇok nadiren kullanıyorum ve asla bir fonksiyonun sonunda değilim .

 if(a) {
   return(a)
 } else {
   return(b)
 }

... bu if(a) a else bdaha iyi okunabilir ve daha az kıvırcık parantezli olarak yeniden yazılabilir . returnBurada gerek yok . "Dönüş" prototipik benim durumum gibi bir şey olurdu ...

ugly <- function(species, x, y){
   if(length(species)>1) stop("First argument is too long.")
   if(species=="Mickey Mouse") return("You're kidding!")
   ### do some calculations 
   if(grepl("mouse", species)) {
      ## do some more calculations
      if(species=="Dormouse") return(paste0("You're sleeping until", x+y))
      ## do some more calculations
      return(paste0("You're a mouse and will be eating for ", x^y, " more minutes."))
      }
   ## some more ugly conditions
   # ...
   ### finally
   return("The end")
   }

Genel olarak, birçok geri dönüşe duyulan ihtiyaç, sorunun çirkin veya kötü yapılandırılmış olduğunu göstermektedir. G

<>

return gerçekten çalışmak için bir işleve ihtiyaç duymaz: değerlendirilecek bir dizi ifadeden çıkmak için kullanabilirsiniz.

getout <- TRUE 
# if getout==TRUE then the value of EXP, LOC, and FUN will be "OUTTA HERE"
# .... if getout==FALSE then it will be `3` for all these variables    

EXP <- eval(expression({
   1
   2
   if(getout) return("OUTTA HERE")
   3
   }))

LOC <- local({
   1
   2
   if(getout) return("OUTTA HERE")
   3
   })

FUN <- (function(){
   1
   2
   if(getout) return("OUTTA HERE")
   3
   })()

identical(EXP,LOC)
identical(EXP,FUN)

Bugün biri aslında gerekebilir bir vakaya rastlanmadı returnbir değer olup olmadığını test etmek gerek varsayalım: (benim çirkin Yukarıdaki örnek oldukça yapaydır) NULLveya NAbaşka şekilde dönmek, bu gibi durumlarda, boş bir dize döndürür: characterdeğeri. Ama bir test is.na(NULL)bir hata verir, bu yüzden sadece ile yapılabilir if(is.null(x)) return("")ve sonra devam edebilir gibi görünüyor if(is.na(x)) ...... (Bir kullanabilirsiniz length(x)==0yerine is.null(x)ama yine de kullanmak mümkün değildir length(x)==0 | is.na(x)eğer xolduğunu NULL.)
lebatsnok

1
Bunun nedeni |(tahminlerin ||sırayla değerlendirildiği, vektörleştirilmemiş, kısa devre VEYA, vektörel değil ) yerine (her iki tarafın değerlendirildiği VEYA vektörlü) kullanmanızdır. Düşünün if (TRUE | stop()) print(1)karşıif (TRUE || stop()) print(1)
ASAC

2

return kod okunabilirliğini artırabilir:

foo <- function() {
    if (a) return(a)       
    b     
}

3
Belki de olabilir. Ancak bunu örneğinizde yapmaz. Bunun yerine kod akışını gizler (veya daha doğrusu karmaşıklaştırır).
Konrad Rudolph

1
fonksiyonunuz basitleştirilebilir: foo <- function() a || b(IMO daha okunabilir; her durumda, "saf" okunabilirlik değil, birinin görüşüne göre okunabilirlik: montaj dilinin mükemmel okunabilir olduğunu söyleyen insanlar var)
lebatsnok

1

Artıklık iddiası burada çok ortaya çıktı. Bence bu ihmal edilecek kadar bir sebep değil return(). Artıklık otomatik olarak kötü bir şey değildir. Stratejik olarak kullanıldığında fazlalık, kodu daha net ve daha bakımlı hale getirir.

Bu örneği düşünün: İşlev parametreleri genellikle varsayılan değerlere sahiptir. Dolayısıyla, varsayılanla aynı olan bir değer belirtmek gereksizdir. Bunun dışında beklediğim davranışı açıkça ortaya koyuyor. Varsayılanların ne olduğunu kendime hatırlatmak için fonksiyon man sayfasını çekmeye gerek yok. Ve fonksiyonun gelecekteki bir versiyonunun varsayılanlarını değiştirmesinden endişe etmeyin.

Görüşme için ihmal edilebilir bir performans cezasıyla return()(burada başkaları tarafından yayınlanan kıyaslamalara göre), doğru ve yanlıştan ziyade stile iner. Bir şeyin "yanlış" olması için açık bir dezavantaj olması gerekir ve buradaki hiç kimse dahil etmenin veya atlamanın return()tutarlı bir dezavantaja sahip olduğunu tatmin edici bir şekilde göstermedi. Çok büyük / küçük harf ve kullanıcıya özel görünüyor.

İşte burada bunun üzerinde duruyorum.

function(){
  #do stuff
  ...
  abcd
}

Yukarıdaki örnekte olduğu gibi "yetim" değişkenlerden rahatsız değilim. Yazmayı abcdbitirmediğim bir ifadenin parçası olacak mıydı ? Kodumda bir ekleme / düzenleme kalıntısı var mı ve silinmesi mi gerekiyor? Yanlışlıkla başka bir yerden bir şey yapıştırdım / taşıdım mı?

function(){
  #do stuff
  ...
  return(abdc)
}

Aksine, bu ikinci örnek, bir kaza veya eksik koddan ziyade, amaçlanan bir dönüş değeri olduğunu açıkça ortaya koymaktadır. Benim için bu fazlalık kesinlikle işe yaramaz.

Tabii ki, fonksiyon bittiğinde ve çalıştıktan sonra geri dönüşü kaldırabilirim. Ancak bunu kaldırmak kendi başına fazladan bir adımdır ve bence return()ilk etapta yer almaktan daha yararsızdır .

Bütün bunlar, return()kısa isimsiz tek katmanlı fonksiyonlarda kullanmıyorum . Burada fonksiyon kodunun büyük bir bölümünü oluşturur ve bu nedenle çoğunlukla kodu daha az okunaklı hale getiren görsel karmaşaya neden olur. Ancak daha büyük resmi olarak tanımlanmış ve adlandırılmış işlevler için, onu kullanıyorum ve muhtemelen devam edecek.


“Abcd, yazmayı bitirmediğim bir ifadenin parçası olacak mıydı?” - Bu yazdığınız diğer ifadelerden nasıl farklı ? Bu, anlaşmazlığımızın çekirdeğini düşünüyorum. Tek başına değişken bir duruşa sahip olmak zorunlu bir programlama dilinde tuhaf olabilir, ancak işlevsel bir programlama dilinde tamamen normaldir ve beklenir. İddia ettiğim sorun, basitçe işlevsel programlamaya aşina olmamanızdır (“ifadeler” yerine “ifadeler” hakkında konuşmanız gerçeği bunu güçlendirir).
Konrad Rudolph

Farklıdır, çünkü diğer ifadelerin her biri daha açık bir şekilde bir şeyler yapar: Bu bir ödev, karşılaştırma, işlev çağrısı ... Evet, ilk kodlama adımım zorunlu dillerdeydi ve hala zorunlu diller kullanıyorum. Diller arasında tekdüze görsel işaretlere sahip olmak (dillerin izin verdiği her yerde) çalışmamı kolaylaştırır. A return()in R'nin hiçbir maliyeti yoktur. Nesnel olarak gereksizdir, ancak "işe yaramaz" olmak öznel yargınızdır. Gereksiz ve yararsız olmak, eşanlamlı olmak zorunda değildir. Burada katılmıyorum.
cymon

Ayrıca yazılım mühendisi veya bilgisayar bilimcisi değilim. Terminoloji kullanımım hakkında çok fazla nüans okuma.
cymon

Sadece açıklığa kavuşturmak için: “Gereksiz ve işe yaramaz olmak, eş anlamlı olmak zorunda değildir. Burada katılmıyorum. ” - Hayır, buna tamamen katılıyorum ve cevabımda açıkça bu noktayı vurguladım. Artıklık yararlı ve hatta çok önemli olabilir . Ancak bunun aktif olarak gösterilmesi gerekiyor, varsayılmıyor. Bunun neden olduğunu düşündüğünüze dair argümanınızı anlıyorum returnve ikna olmasam da potansiyel olarak geçerli olduğunu düşünüyorum (kesinlikle zorunlu bir dilde… inancım işlevsel dillere tercüme edilmediğidir).
Konrad Rudolph
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.