Secara eksplisit memanggil kembali dalam suatu fungsi atau tidak

199

Beberapa waktu yang lalu saya ditegur oleh Simon Urbanek dari tim inti R (saya percaya) karena merekomendasikan pengguna untuk secara eksplisit menelepon returndi akhir fungsi (komentarnya sudah dihapus):

foo = function() {
  return(value)
}

sebaliknya dia merekomendasikan:

foo = function() {
  value
}

Mungkin dalam situasi seperti ini diperlukan:

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

Komentarnya menjelaskan mengapa tidak menelepon return kecuali sangat dibutuhkan adalah hal yang baik, tetapi ini telah dihapus.

Pertanyaan saya adalah: Mengapa tidak menelepon returnlebih cepat atau lebih baik, dan karenanya lebih disukai?

Paul Hiemstra
sumber
12
returntidak perlu bahkan dalam contoh terakhir. Menghapus returnmungkin membuatnya sedikit lebih cepat, tetapi dalam pandangan saya ini karena R dikatakan sebagai bahasa pemrograman fungsional.
kohske
4
@ kohske Bisakah Anda memperluas komentar Anda menjadi sebuah jawaban, termasuk lebih banyak detail tentang mengapa lebih cepat, dan bagaimana ini terkait dengan R menjadi bahasa pemrograman fungsional?
Paul Hiemstra
2
returnmenginduksi lompatan non-lokal, dan lompatan non-lokal eksplisit tidak biasa untuk FP. Sebenarnya, misalnya, skema tidak punya return. Saya pikir komentar saya terlalu pendek (dan mungkin salah) sebagai jawaban.
kohske
2
F # tidak memiliki return, break, continuebaik, yang kadang-kadang membosankan.
colinfang

Jawaban:

129

Pertanyaannya adalah: Mengapa tidak (secara eksplisit) menelepon kembali lebih cepat atau lebih baik, dan karenanya lebih disukai?

Tidak ada pernyataan dalam dokumentasi R yang membuat asumsi seperti itu.
Halaman utama? 'Function' mengatakan:

function( arglist ) expr
return(value)

Apakah lebih cepat tanpa menelepon kembali?

Keduanya function()dan return()merupakan fungsi primitif dan function()itu sendiri mengembalikan nilai yang dievaluasi terakhir bahkan tanpa menyertakan return()fungsi.

Memanggil return()sebagai .Primitive('return')dengan itu nilai terakhir sebagai argumen akan melakukan pekerjaan yang sama tetapi membutuhkan satu panggilan lagi. Sehingga .Primitive('return')panggilan ini (seringkali) tidak perlu dapat menarik sumber daya tambahan. Namun pengukuran sederhana menunjukkan bahwa perbedaan yang dihasilkan sangat kecil dan karenanya tidak dapat menjadi alasan untuk tidak menggunakan pengembalian eksplisit. Plot berikut dibuat dari data yang dipilih dengan cara ini:

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

Fungsi perbandingan waktu berlalu

Gambar di atas mungkin sedikit berbeda pada platform Anda. Berdasarkan data yang diukur, ukuran objek yang dikembalikan tidak menyebabkan perbedaan, jumlah pengulangan (bahkan jika ditingkatkan) membuat perbedaan yang sangat kecil, yang dalam kata nyata dengan data nyata dan algoritma nyata tidak dapat dihitung atau membuat Anda skrip berjalan lebih cepat.

Apakah lebih baik tanpa menelepon kembali?

Return adalah alat yang baik untuk secara jelas merancang "daun" kode di mana rutin harus berakhir, keluar dari fungsi dan mengembalikan nilai.

# 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
> 

Itu tergantung pada strategi dan gaya pemrograman programmer gaya apa yang dia gunakan, dia tidak dapat menggunakan return () karena tidak diperlukan.

Pemrogram inti R menggunakan kedua pendekatan tersebut yaitu. dengan dan tanpa pengembalian eksplisit () karena dimungkinkan untuk menemukan di sumber fungsi 'basis'.

Berkali-kali hanya return () digunakan (tidak ada argumen) mengembalikan NULL dalam kasus untuk menghentikan fungsi secara kondisional.

Tidak jelas apakah lebih baik atau tidak sebagai pengguna standar atau analis yang menggunakan R tidak dapat melihat perbedaan nyata.

Pendapat saya adalah bahwa pertanyaannya seharusnya: Apakah ada bahaya dalam menggunakan pengembalian eksplisit yang berasal dari implementasi R?

Atau, mungkin lebih baik, pengguna menulis kode fungsi harus selalu bertanya: Apa efek tidak menggunakan pengembalian eksplisit (atau menempatkan objek untuk dikembalikan sebagai daun terakhir dari cabang kode) dalam kode fungsi?

Petr Matousu
sumber
4
Terima kasih atas jawaban yang sangat bagus. Saya percaya tidak ada bahaya dalam menggunakan return, dan itu tergantung pada preferensi programmer apakah akan menggunakannya atau tidak.
Paul Hiemstra
38
Kecepatan returnadalah hal terakhir yang harus Anda khawatirkan.
Hadley
2
Saya pikir ini adalah jawaban yang buruk. Alasan datang ke ketidaksepakatan mendasar dari nilai menggunakan returnpanggilan fungsi yang tidak perlu . Pertanyaan yang harus Anda ajukan bukanlah pertanyaan yang Anda ajukan pada akhirnya. Alih-alih, ini: “mengapa saya harus menggunakan redundan return? Manfaat apa yang diberikannya? ” Ternyata, jawabannya adalah "tidak banyak", atau bahkan "tidak sama sekali". Balasan Anda gagal menghargai ini.
Konrad Rudolph
@KonradRudolph ... Anda mengulangi sebenarnya apa yang awalnya ditanyakan Paul (mengapa pengembalian eksplisit buruk). Dan saya ingin tahu benar (yang tepat untuk siapa pun) menjawab juga :). Apakah Anda mempertimbangkan untuk memberikan penjelasan untuk pengguna situs ini?
Petr Matousu
1
@ Alasan Saya di tempat lain menautkan pos Reddit yang menjelaskan mengapa argumen ini cacat dalam konteks ini. Sayangnya komentar tersebut sepertinya dihapus secara otomatis setiap saat. Versi singkatnya returnadalah seperti komentar eksplisit yang mengatakan “increment x by 1”, di sebelah kode yang dikerjakan x = x + 2. Dengan kata lain, kesaksiannya adalah (a) sama sekali tidak relevan, dan (b) itu menyampaikan informasi yang salah . Karena returnsemantik dalam R adalah, murni, "batalkan fungsi ini". Itu tidak berarti sama dengan returndi bahasa lain.
Konrad Rudolph
103

Jika semua orang setuju itu

  1. return tidak perlu di akhir fungsi tubuh
  2. tidak menggunakan returnsedikit lebih cepat (menurut uji @ Alan, 4,3 mikrodetik versus 5.1)

haruskah kita semua berhenti menggunakan return di akhir fungsi? Saya tentu tidak mau, dan saya ingin menjelaskan alasannya. Saya berharap mendengar jika orang lain membagikan pendapat saya. Dan saya minta maaf jika ini bukan jawaban langsung untuk OP, tetapi lebih seperti komentar subyektif yang panjang.

Masalah utama saya dengan tidak menggunakan returnadalah bahwa, seperti yang ditunjukkan Paul, ada tempat lain di tubuh fungsi di mana Anda mungkin membutuhkannya. Dan jika Anda terpaksa menggunakan returnsuatu tempat di tengah fungsi Anda, mengapa tidak membuat semuanyareturn pernyataan eksplisit? Aku benci menjadi tidak konsisten. Saya juga berpikir bahwa kode membaca lebih baik; seseorang dapat memindai fungsi dan dengan mudah melihat semua titik keluar dan nilai-nilai.

Paulus menggunakan contoh ini:

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

Sayangnya, orang dapat menunjukkan bahwa itu dapat dengan mudah ditulis ulang sebagai:

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

Versi terakhir bahkan sesuai dengan beberapa standar pemrograman kode yang menganjurkan satu pernyataan kembali per fungsi. Saya pikir contoh yang lebih baik adalah:

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

Ini akan jauh lebih sulit untuk ditulis ulang menggunakan pernyataan pengembalian tunggal: itu akan membutuhkan banyak breaks dan sistem variabel boolean yang rumit untuk menyebarkannya. Semua ini untuk mengatakan bahwa aturan pengembalian tunggal tidak cocok dengan R. Jadi jika Anda perlu menggunakannya returndi beberapa tempat di tubuh fungsi Anda, mengapa tidak konsisten dan menggunakannya di mana-mana?

Saya tidak berpikir argumen kecepatan itu valid. Perbedaan 0,8 mikrodetik tidak ada artinya ketika Anda mulai melihat fungsi yang benar-benar melakukan sesuatu. Hal terakhir yang bisa saya lihat adalah kurang mengetik tapi hei, saya tidak malas.

flodel
sumber
7
+1, ada kebutuhan yang jelas untuk returnpernyataan dalam beberapa kasus, seperti yang ditunjukkan @flodel. Atau, ada situasi di mana pernyataan kembali dihilangkan, misalnya banyak dan banyak panggilan fungsi kecil. Dalam semua yang lain, katakanlah 95%, dari kasus-kasus itu tidak terlalu penting apakah seseorang menggunakan returnatau tidak, dan itu menjadi pilihan. Saya suka menggunakan return karena lebih eksplisit dalam apa yang Anda maksud, sehingga lebih mudah dibaca. Mungkin diskusi ini mirip dengan <-vs =?
Paul Hiemstra
7
Ini memperlakukan R sebagai bahasa pemrograman imperatif, yang bukan: bahasa pemrograman fungsional. Pemrograman fungsional hanya bekerja secara berbeda, dan menggunakan returnuntuk mengembalikan nilai tidak masuk akal, setara dengan menulis, if (x == TRUE)bukan if (x).
Konrad Rudolph
4
Anda juga menulis ulang foosesuai foo <- function(x) if (a) a else b(dengan linebreaks sesuai kebutuhan). Tidak perlu untuk pengembalian eksplisit atau nilai menengah.
Hadley
26

Ini diskusi yang menarik. Saya pikir contoh @ flodel sangat bagus. Namun, saya pikir ini mengilustrasikan poin saya (dan @koshke menyebutkan ini dalam komentar) yang returnmasuk akal ketika Anda menggunakan imperatif alih-alih gaya pengkodean fungsional .

Bukan untuk mempercayai intinya, tetapi saya akan menulis ulang fooseperti ini:

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

Gaya fungsional menghindari perubahan status, seperti menyimpan nilai output. Dalam gaya ini, returntidak pada tempatnya; foolebih mirip fungsi matematika.

Saya setuju dengan @flodel: menggunakan sistem variabel boolean yang rumit di barakan menjadi kurang jelas, dan tidak ada gunanya saat Anda memilikinya return. Apa yang membuatnya barbegitu menerimareturn pernyataan adalah bahwa itu ditulis dalam gaya imperatif. Memang, variabel boolean mewakili perubahan "negara" yang dihindari dengan gaya fungsional.

Sangat sulit untuk menulis ulang bardengan gaya fungsional, karena ini hanya pseudocode, tetapi idenya adalah seperti ini:

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.
}

The whileLoop akan menjadi yang paling sulit untuk menulis ulang, karena dikendalikan oleh negara perubahan kea .

Kehilangan kecepatan yang disebabkan oleh panggilan ke returndiabaikan, tetapi efisiensi yang diperoleh dengan menghindari returndan menulis ulang dengan gaya fungsional sering kali sangat besar. Memberitahu pengguna baru untuk berhenti menggunakan returnmungkin tidak akan membantu, tetapi membimbing mereka ke gaya fungsional akan memberikan hasil.


@ Paul returndiperlukan dalam gaya imperatif karena Anda sering ingin keluar dari fungsi pada titik yang berbeda dalam satu lingkaran. Gaya fungsional tidak menggunakan loop, dan karenanya tidak perlu return. Dalam gaya fungsional murni, panggilan terakhir hampir selalu merupakan nilai pengembalian yang diinginkan.

Dalam Python, fungsi membutuhkan returnpernyataan. Namun, jika Anda memprogram fungsi Anda dalam gaya fungsional, kemungkinan Anda hanya akan memiliki satureturn pernyataan: di akhir fungsi Anda.

Menggunakan contoh dari posting StackOverflow lain, katakanlah kita menginginkan fungsi yang mengembalikan TRUEjika semua nilai dalam suatu diberi xmemiliki panjang ganjil. Kita bisa menggunakan dua gaya:

# 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)

Dalam gaya fungsional, nilai yang akan dikembalikan secara alami jatuh di ujung fungsi. Sekali lagi, ini lebih mirip fungsi matematika.

@GSee Peringatan yang diuraikan dalam ?ifelsepasti menarik, tapi saya tidak berpikir mereka mencoba untuk menghalangi penggunaan fungsi. Bahkan, ifelsememiliki keunggulan fungsi vectorizing secara otomatis. Misalnya, pertimbangkan versi yang sedikit dimodifikasi foo:

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

Fungsi ini berfungsi dengan baik ketika length(a)1. Tetapi jika Anda menulis ulang foodenganifelse

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

Sekarang foobekerja pada ukuran berapa pun a. Bahkan, itu akan berfungsi ketika amatriks. Mengembalikan nilai dengan bentuk yang sama testdengan fitur yang membantu vektorisasi, bukan masalah.

memotret
sumber
Tidak jelas bagi saya mengapa returntidak cocok dengan gaya pemrograman fungsional. Jika seseorang memprogram secara imperatif atau fungsional, pada tahap tertentu suatu fungsi atau subrutin perlu mengembalikan sesuatu. Sebagai contoh, pemrograman fungsional dalam python masih membutuhkan returnpernyataan. Bisakah Anda menguraikan lebih lanjut tentang hal ini.
Paul Hiemstra
Dalam situasi ini, menggunakan ifelse(a,a,b)adalah hewan peliharaan saya kesal. Sepertinya setiap baris ?ifelseberteriak, "jangan gunakan aku, bukan if (a) {a} else b." mis. "... mengembalikan nilai dengan bentuk yang sama dengan test", "jika yesatau noterlalu pendek, elemen-elemennya didaur ulang.", "mode hasil mungkin tergantung pada nilai test", "atribut kelas dari hasil diambil dari testdan mungkin tidak sesuai untuk nilai yang dipilih dari yesdan no"
GSee
Pada pandangan kedua, footidak masuk akal; itu akan selalu mengembalikan BENAR atau b. Menggunakannya ifelseakan mengembalikan 1 atau beberapa TRUE, dan / atau 1 atau beberapa bs. Awalnya, saya pikir maksud dari fungsi itu adalah untuk mengatakan "jika beberapa pernyataan BENAR, kembalikan sesuatu, jika tidak, kembalikan sesuatu yang lain." Saya tidak berpikir itu harus di-vektor-kan, karena dengan begitu akan menjadi "kembalikan elemen-elemen dari beberapa objek yang BENAR, dan untuk semua elemen yang tidak BENAR, kembalikan b.
GSee
22

Tampaknya tanpa return()itu lebih cepat ...

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

____ EDIT__ _ __ _ __ _ __ _ __ _ ___

Saya melanjutkan ke tolok ukur orang lain (benchmark(fuu(x),foo(x),replications=1e7) ) dan hasilnya terbalik ... Saya akan mencoba di server.

Alan
sumber
Bisakah Anda mengomentari alasan mengapa perbedaan ini terjadi?
Paul Hiemstra
4
Jawaban @PaulHiemstra Petr mencakup salah satu alasan utama untuk ini; dua panggilan saat menggunakan return(), satu jika Anda tidak. Ini benar-benar berlebihan pada akhir fungsi karena function()mengembalikan nilai terakhirnya. Anda hanya akan memperhatikan hal ini dalam banyak pengulangan fungsi di mana tidak banyak yang dilakukan secara internal sehingga biaya return()menjadi bagian besar dari total waktu komputasi fungsi.
Gavin Simpson
13

Masalah dengan tidak menempatkan 'kembali' secara eksplisit di akhir adalah bahwa jika seseorang menambahkan pernyataan tambahan di akhir metode, tiba-tiba nilai kembali salah:

foo <- function() {
    dosomething()
}

Ini mengembalikan nilai dosomething().

Sekarang kami datang keesokan harinya dan menambahkan baris baru:

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

Kami ingin kode kami mengembalikan nilai dosomething(), tetapi sebaliknya tidak lagi.

Dengan pengembalian eksplisit, ini menjadi sangat jelas:

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

Kita dapat melihat bahwa ada sesuatu yang aneh dengan kode ini, dan memperbaikinya:

foo <- function() {
    dosomething2()
    return( dosomething() )
}
Hugh Perkins
sumber
1
ya, sebenarnya saya menemukan bahwa pengembalian eksplisit () berguna ketika debugging; begitu kode dibersihkan, kebutuhannya kurang menarik dan saya lebih suka keanggunan tidak memilikinya ...
PatrickT
Tapi ini sebenarnya bukan masalah dalam kode nyata, ini murni teoretis. Kode yang dapat mengalami hal ini memiliki masalah yang jauh lebih besar: alur kode yang rapuh dan tidak jelas yang sangat tidak jelas sehingga penambahan sederhana memecahkannya.
Konrad Rudolph
@KonradRudolph Saya pikir Anda agak melakukan No-True Scotsman di atasnya ;-) "Jika ini masalah dalam kode Anda, Anda adalah programmer yang buruk!". Saya benar-benar tidak setuju. Saya pikir sementara Anda bisa keluar dengan mengambil jalan pintas pada potongan-potongan kecil kode, di mana Anda tahu setiap baris dengan hati, ini akan kembali menggigit Anda karena kode Anda semakin besar.
Hugh Perkins
2
@HughPerkins Bukan Orang Skotlandia sejati ; melainkan, ini merupakan pengamatan empiris tentang kompleksitas kode, yang didukung oleh praktik terbaik rekayasa perangkat lunak selama beberapa dekade: menjaga fungsi individu tetap singkat dan aliran kode tetap jelas. Dan menghilangkan returnbukanlah jalan pintas, itu gaya yang tepat dalam pemrograman fungsional. Menggunakan returnpanggilan fungsi yang tidak perlu adalah contoh pemrograman pemujaan kargo .
Konrad Rudolph
Yah ... Saya tidak melihat bagaimana ini mencegah Anda menambahkan sesuatu setelah returnpernyataan Anda dan tidak menyadari bahwa itu tidak akan dieksekusi. Anda bisa menambahkan komentar setelah nilai yang ingin Anda kembalikan misalnyadosomething() # this is my return value, don't add anything after it unless you know goddam well what you are doing
lebatsnok
10

Pertanyaan saya adalah: Mengapa tidak menelepon returnlebih cepat

Ini lebih cepat karena returnmerupakan fungsi (primitif) dalam R, yang berarti bahwa menggunakannya dalam kode menimbulkan biaya pemanggilan fungsi. Bandingkan ini dengan sebagian besar bahasa pemrograman lain, di mana returnkata kunci, tetapi bukan panggilan fungsi: ini tidak diterjemahkan ke setiap eksekusi kode runtime.

Yang mengatakan, memanggil fungsi primitif dengan cara ini cukup cepat di R, dan memanggil returnmenimbulkan overhead yang sangat kecil. Ini bukan argumen untuk menghilangkan return.

atau lebih baik, dan karenanya lebih disukai?

Karena tidak ada alasan untuk menggunakannya.

Karena itu berlebihan, dan itu tidak menambahkan redudansi yang berguna .

Untuk menjadi jelas: redundansi dapat kadang-kadang berguna . Tetapi kebanyakan redundansi tidak seperti ini. Alih-alih, itu adalah jenis yang menambahkan kekacauan visual tanpa menambahkan informasi: itu adalah pemrograman yang setara dengan kata pengisi atau bagan sampah ).

Pertimbangkan contoh komentar penjelasan berikut, yang secara universal diakui sebagai redundansi buruk karena komentar tersebut hanya memparafrasekan apa yang sudah diungkapkan oleh kode:

# Add one to the result
result = x + 1

Menggunakan returndalam R jatuh dalam kategori yang sama, karena R adalah bahasa pemrograman fungsional , dan dalam R setiap panggilan fungsi memiliki nilai . Ini adalah properti fundamental dari R. Dan begitu Anda melihat kode R dari perspektif bahwa setiap ekspresi (termasuk setiap panggilan fungsi) memiliki nilai, pertanyaannya kemudian menjadi: "mengapa saya harus menggunakan return?" Perlu ada alasan positif, karena standarnya bukan untuk menggunakannya.

Salah satu alasan positif tersebut adalah untuk memberi sinyal keluar awal dari suatu fungsi, misalnya dalam klausa penjaga :

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

Ini adalah, valid penggunaan non-berlebihan dari return. Namun, klausa penjaga seperti itu jarang di R dibandingkan dengan bahasa lain, dan karena setiap ekspresi memiliki nilai, reguler iftidak memerlukan return:

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

Kami bahkan dapat menulis ulang fseperti ini:

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

... dimana if (cond) exprsama dengan if (cond) expr else NULL.

Akhirnya, saya ingin mencegah tiga keberatan umum:

  1. Beberapa orang berpendapat bahwa menggunakan returnmenambahkan kejelasan, karena ini menandakan "fungsi ini mengembalikan nilai". Tetapi seperti yang dijelaskan di atas, setiap fungsi mengembalikan sesuatu dalam R. Berpikir returnsebagai penanda untuk mengembalikan nilai tidak hanya berlebihan, secara aktif menyesatkan .

  2. Terkait, Zen Python memiliki pedoman luar biasa yang harus selalu diikuti:

    Eksplisit lebih baik daripada implisit.

    Bagaimana menjatuhkan berlebihan returntidak melanggar ini? Karena nilai pengembalian fungsi dalam bahasa fungsional selalu eksplisit: itu adalah ekspresi terakhirnya. Lagi-lagi ini argumen yang sama tentang explicitness vs redundancy.

    Faktanya, jika Anda ingin saksi mata, gunakan itu untuk menyorot pengecualian pada aturan: tandai fungsi yang tidak mengembalikan nilai bermakna, yang hanya dipanggil untuk efek sampingnya (seperti cat). Kecuali R memiliki penanda yang lebih baik daripada returnuntuk kasus ini: invisible. Sebagai contoh, saya akan menulis

    save_results = function (results, file) {
        # … code that writes the results to a file …
        invisible()
    }
  3. Tapi bagaimana dengan fungsi yang panjang? Tidakkah mudah kehilangan jejak apa yang dikembalikan?

    Dua jawaban: pertama, tidak juga. Aturannya jelas: ekspresi terakhir dari suatu fungsi adalah nilainya. Tidak ada yang perlu dilacak.

    Tetapi yang lebih penting, masalah dalam fungsi yang panjang bukanlah kurangnya returnpenanda eksplisit . Itu panjang fungsinya . Fungsi panjang hampir (?) Selalu melanggar prinsip tanggung jawab tunggal dan bahkan jika tidak, mereka akan mendapat manfaat dari dipisahkan untuk dibaca.

Konrad Rudolph
sumber
Mungkin saya harus menambahkan bahwa beberapa orang menganjurkan penggunaan returnagar lebih mirip dengan bahasa lain. Tapi itu argumen yang buruk: bahasa pemrograman fungsional lainnya cenderung tidak menggunakan returnkeduanya. Ini hanya bahasa imperatif , di mana tidak setiap ekspresi memiliki nilai, yang menggunakannya.
Konrad Rudolph
Saya sampai pada Pertanyaan ini dengan pendapat bahwa menggunakan returndukungan secara eksplisit lebih baik, dan membaca jawaban Anda dengan kritik penuh. Jawaban Anda telah membuat saya merenungkan pandangan itu. Saya berpikir bahwa kebutuhan untuk menggunakan returnsecara eksplisit (setidaknya dalam kasus saya sendiri), terkait dengan kebutuhan untuk dapat lebih baik untuk merevisi fungsi saya di titik waktu kemudian. Dengan gagasan bahwa fungsi saya mungkin terlalu kompleks, saya sekarang dapat melihat bahwa tujuan untuk meningkatkan gaya pemrograman saya, adalah berusaha untuk menyusun kode untuk mempertahankan kejelasan tanpa a return. Terima kasih atas refleksi dan wawasan itu !!
Kasper Thystrup Karstensen
6

Saya anggap returnsebagai trik. Sebagai aturan umum, nilai ekspresi terakhir yang dievaluasi dalam suatu fungsi menjadi nilai fungsi - dan pola umum ini ditemukan di banyak tempat. Semua yang berikut ini dievaluasi menjadi 3:

local({
1
2
3
})

eval(expression({
1
2
3
}))

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

Apa returnyang tidak benar-benar mengembalikan nilai (ini dilakukan dengan atau tanpa itu) tetapi "keluar" dari fungsi dengan cara yang tidak teratur. Dalam hal itu, itu adalah setara dengan pernyataan GOTO terdekat dalam R (ada juga break dan next). Saya menggunakan returnsangat jarang dan tidak pernah di akhir fungsi.

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

... ini dapat ditulis ulang karena if(a) a else blebih mudah dibaca dan tidak terlalu keriting. Tidak perlu returnsama sekali di sini. Kasus prototipikal saya tentang penggunaan "pengembalian" akan menjadi seperti ...

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")
   }

Secara umum, kebutuhan akan banyak pengembalian menunjukkan bahwa masalahnya buruk atau terstruktur dengan buruk

<>

return tidak benar-benar membutuhkan fungsi untuk bekerja: Anda dapat menggunakannya untuk keluar dari rangkaian ekspresi yang akan dievaluasi.

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)
lebatsnok
sumber
Hari ini saya menemukan sebuah kasus di mana seseorang mungkin benar-benar membutuhkan return(contoh jelek saya di atas sangat buatan): misalkan Anda perlu menguji apakah suatu nilai NULLatau NA: dalam kasus ini, kembalikan string kosong, jika tidak kembalikan characternilainya. Tapi tes is.na(NULL)memberikan kesalahan, jadi sepertinya itu hanya bisa dilakukan if(is.null(x)) return("")dan dilanjutkan if(is.na(x)) ...... (Satu dapat menggunakan length(x)==0bukannya is.null(x)tapi tetap saja, tidak mungkin untuk menggunakan length(x)==0 | is.na(x)jika xadalah NULL.)
lebatsnok
1
Itu karena Anda menggunakan |(vektorisasi ATAU di mana kedua sisi dievaluasi) alih-alih ||(korsleting ATAU, tidak vektorisasi, di mana predikat dievaluasi pada gilirannya). Pertimbangkan if (TRUE | stop()) print(1)versusif (TRUE || stop()) print(1)
asac
2

return dapat meningkatkan keterbacaan kode:

foo <- function() {
    if (a) return(a)       
    b     
}
Eldar Agalarov
sumber
3
Mungkin bisa, pada saat itu. Tapi itu tidak melakukannya dalam contoh Anda. Alih-alih itu mengaburkan (atau lebih tepatnya, memperumit) aliran kode.
Konrad Rudolph
1
fungsi Anda dapat disederhanakan menjadi: foo <- function() a || b(yang IMO lebih mudah dibaca; dalam hal apa pun, tidak ada keterbacaan "murni" tetapi keterbacaan menurut pendapat seseorang: ada orang yang mengatakan bahasa assembly mudah dibaca)
lebatsnok
1

Argumen redundansi telah banyak muncul di sini. Menurut saya itu bukan alasan yang cukup untuk dihilangkan return(). Redundansi tidak secara otomatis merupakan hal yang buruk. Ketika digunakan secara strategis, redundansi membuat kode lebih jelas dan lebih dapat dikelola.

Pertimbangkan contoh ini: Parameter fungsi sering memiliki nilai default. Jadi, tentukan nilai yang sama dengan standarnya adalah redundan. Kecuali itu membuat jelas perilaku yang saya harapkan. Tidak perlu membuka halaman fungsi untuk mengingatkan diri saya apa defaultnya. Dan tidak perlu khawatir tentang versi masa depan dari fungsi yang mengubah standarnya.

Dengan penalti kinerja yang dapat diabaikan untuk pemanggilan return()(sesuai dengan tolok ukur yang diposting di sini oleh orang lain) itu menjadi gaya daripada benar dan salah. Agar sesuatu menjadi "salah", harus ada kerugian yang jelas, dan tidak ada orang di sini yang telah menunjukkan dengan memuaskan bahwa memasukkan atau menghilangkan return()memiliki kerugian yang konsisten. Tampaknya sangat spesifik kasus dan khusus pengguna.

Jadi di sinilah saya berdiri di sini.

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

Saya tidak nyaman dengan variabel "yatim" seperti pada contoh di atas. Apakah abcdakan menjadi bagian dari pernyataan yang belum selesai saya tulis? Apakah ini merupakan sisa dari sambungan / edit dalam kode saya dan perlu dihapus? Apakah saya secara tidak sengaja menempel / memindahkan sesuatu dari tempat lain?

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

Sebaliknya, contoh kedua ini membuat jelas bagi saya bahwa itu adalah nilai pengembalian yang dimaksudkan, daripada beberapa kode kecelakaan atau tidak lengkap. Bagi saya redundansi ini sama sekali tidak sia-sia.

Tentu saja, begitu fungsi selesai dan bekerja saya bisa menghapus pengembalian. Tetapi menghapus itu sendiri merupakan langkah ekstra yang berlebihan, dan menurut saya lebih berguna daripada termasuk return()di tempat pertama.

Semua yang dikatakan, saya tidak menggunakan return()fungsi satu-liner pendek tanpa nama. Itu membuat sebagian besar dari kode fungsi dan karena itu sebagian besar menyebabkan kekacauan visual yang membuat kode kurang terbaca. Tetapi untuk fungsi yang lebih besar didefinisikan dan dinamai secara resmi, saya menggunakannya dan kemungkinan akan terus demikian.

cymon
sumber
"Apakah abcd akan menjadi bagian dari pernyataan yang belum selesai kutulis?" - Bagaimana bedanya dengan ungkapan lain yang Anda tulis? Ini, saya pikir adalah inti dari ketidaksetujuan kami. Memiliki variabel berdiri sendiri mungkin aneh dalam bahasa pemrograman imperatif tetapi itu benar-benar normal dan diharapkan dalam bahasa pemrograman fungsional. Masalahnya, saya klaim, hanyalah bahwa Anda tidak terbiasa dengan pemrograman fungsional (fakta bahwa Anda berbicara tentang "pernyataan" dan bukan "ekspresi" memperkuat ini).
Konrad Rudolph
Ini berbeda karena setiap pernyataan lainnya biasanya melakukan sesuatu dengan cara yang lebih jelas: Ini adalah tugas, perbandingan, panggilan fungsi ... Ya langkah pengkodean pertama saya dalam bahasa imperatif dan saya masih menggunakan bahasa imperatif. Memiliki isyarat visual yang seragam di berbagai bahasa (di mana pun bahasa memungkinkan) membuat pekerjaan saya lebih mudah. A return()in R tidak mengeluarkan biaya. Itu secara objektif berlebihan, tetapi menjadi "tidak berguna" adalah penilaian subyektif Anda. Redundan dan tidak berguna tidak selalu sama artinya. ITULAH di mana kita tidak setuju.
cymon
Juga, saya bukan insinyur perangkat lunak atau ilmuwan komputer. Jangan terlalu banyak membaca nuansa penggunaan terminologi saya.
cymon
Hanya untuk mengklarifikasi: “Redundan dan tidak berguna tidak selalu sama artinya. ITULAH di mana kita tidak setuju. ” - Tidak, saya setuju sepenuhnya dengan itu, dan saya secara eksplisit menyatakan hal itu dalam jawaban saya. Redundansi dapat membantu atau bahkan penting . Tetapi ini perlu ditunjukkan secara aktif, bukan diasumsikan. Saya mengerti argumen Anda mengapa Anda pikir ini berlaku return, dan meskipun saya tidak yakin saya pikir ini berpotensi valid (pasti dalam bahasa imperatif ... keyakinan saya adalah bahwa itu tidak diterjemahkan ke bahasa fungsional).
Konrad Rudolph