โทรกลับอย่างชัดเจนในฟังก์ชั่นหรือไม่


199

ในขณะที่ฉันถูก rebukedโดย Simon Urbanek จากทีม R core (ฉันเชื่อว่า) สำหรับการแนะนำผู้ใช้ให้เรียกอย่างชัดเจนreturnในตอนท้ายของฟังก์ชั่น (ความคิดเห็นของเขาถูกลบแม้ว่า):

foo = function() {
  return(value)
}

เขาแนะนำแทน:

foo = function() {
  value
}

อาจเป็นในสถานการณ์เช่นนี้มันเป็นสิ่งจำเป็น:

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

ความคิดเห็นของเขาทำให้เข้าใจได้ว่าทำไมไม่โทรหาreturnถ้าไม่ต้องการอย่างเคร่งครัดก็เป็นเรื่องดี

คำถามของฉันคือทำไมไม่โทรreturnเร็วหรือดีกว่าและดีกว่า?


12
returnไม่จำเป็นแม้แต่ในตัวอย่างสุดท้าย การนำออกreturnอาจทำให้เร็วขึ้นเล็กน้อย แต่ในมุมมองของฉันนี่เป็นเพราะ R ถูกกล่าวว่าเป็นภาษาที่ใช้ในการเขียนโปรแกรม
kohske

4
@kohske คุณสามารถขยายความคิดเห็นของคุณเป็นคำตอบรวมถึงรายละเอียดเพิ่มเติมว่าทำไมมันถึงเร็วกว่าและสิ่งนี้เกี่ยวข้องกับ R ในการเป็นภาษาโปรแกรมที่ใช้งานได้หรือไม่?
พอล Hiemstra

2
returnก่อให้เกิดการกระโดดที่ไม่ใช่ของท้องถิ่นและการกระโดดที่ไม่ใช่ของท้องถิ่นนั้นผิดปกติสำหรับ FP returnที่จริงตัวอย่างเช่นโครงการไม่ได้ ฉันคิดว่าความคิดเห็นของฉันสั้นเกินไป (และอาจไม่ถูกต้อง) เป็นคำตอบ
kohske

2
F # ไม่ได้return, break, continueอย่างใดอย่างหนึ่งซึ่งบางครั้งก็น่าเบื่อ
colinfang

คำตอบ:


129

คำถามคือ: เหตุใดการโทรกลับไม่ชัดเจน (ดีกว่า) จึงดีกว่า

ไม่มีคำสั่งในเอกสาร R ทำให้สมมติฐานดังกล่าว
หน้าหลัก? 'ฟังก์ชั่น' พูดว่า:

function( arglist ) expr
return(value)

มันเร็วกว่าโดยไม่ต้องโทรกลับหรือไม่?

ทั้งสองfunction()และreturn()มีฟังก์ชั่นแบบดั้งเดิมและfunction()ตัวเองกลับค่าการประเมินที่ผ่านมาแม้จะไม่ได้รวมถึงreturn()ฟังก์ชั่น

การเรียกreturn()เช่นเดียว.Primitive('return')กับค่าสุดท้ายนั้นเป็นอาร์กิวเมนต์จะทำงานเดียวกัน แต่ต้องการการโทรมากกว่าหนึ่งครั้ง เพื่อให้การ.Primitive('return')โทรที่ไม่จำเป็นนี้บ่อยครั้งสามารถดึงทรัพยากรเพิ่มเติมได้ การวัดอย่างง่าย ๆ แสดงให้เห็นว่าความแตกต่างที่เกิดขึ้นนั้นมีขนาดเล็กมากจึงไม่สามารถเป็นเหตุผลที่ไม่ใช้ผลตอบแทนที่ชัดเจน พล็อตต่อไปนี้สร้างจากข้อมูลที่เลือกด้วยวิธีนี้:

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

ฟังก์ชั่นการเปรียบเทียบเวลาที่ผ่านไป

รูปภาพด้านบนอาจแตกต่างเล็กน้อยบนแพลตฟอร์มของคุณ จากข้อมูลที่วัดได้ขนาดของวัตถุที่คืนจะไม่ก่อให้เกิดความแตกต่างจำนวนการทำซ้ำ (แม้ว่าจะขยายขึ้น) ทำให้มีความแตกต่างเพียงเล็กน้อยซึ่งในความเป็นจริงด้วยข้อมูลจริงและอัลกอริธึมจริงไม่สามารถนับได้ สคริปต์ทำงานได้เร็วขึ้น

จะดีกว่าหรือไม่ถ้าไม่โทรกลับ

Return เป็นเครื่องมือที่ดีสำหรับการออกแบบ "ใบ" ของรหัสอย่างชัดเจนซึ่งงานประจำควรสิ้นสุดกระโดดออกจากฟังก์ชั่นและคืนค่า

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

มันขึ้นอยู่กับกลยุทธ์และรูปแบบการเขียนโปรแกรมของโปรแกรมเมอร์ว่าเขาใช้รูปแบบใดเขาสามารถใช้ไม่ส่งคืน () ตามที่ไม่จำเป็น

โปรแกรมเมอร์แกน R ใช้ทั้งสองวิธีคือ โดยมีและไม่มีการส่งคืน () อย่างชัดเจนเนื่องจากสามารถค้นหาได้ในแหล่งที่มาของฟังก์ชัน 'ฐาน'

มีการใช้ return () หลายครั้ง (ไม่มีอาร์กิวเมนต์) ที่ส่งคืนค่า NULL ในกรณีต่างๆเพื่อหยุดการทำงานของเงื่อนไข

ไม่ชัดเจนหากดีกว่าหรือไม่ในฐานะผู้ใช้มาตรฐานหรือนักวิเคราะห์ที่ใช้ R ไม่สามารถเห็นความแตกต่างที่แท้จริงได้

ความคิดเห็นของฉันคือคำถามควรจะเป็น: มีอันตรายใด ๆ ในการใช้ผลตอบแทนที่ชัดเจนมาจากการใช้งาน R?

หรืออาจจะดีกว่ารหัสฟังก์ชั่นการเขียนของผู้ใช้ควรถามเสมอว่า: อะไรคือผลที่เกิดขึ้นเมื่อไม่ใช้การส่งคืนอย่างชัดเจน


4
ขอบคุณสำหรับคำตอบที่ดีมาก ฉันเชื่อว่าไม่มีอันตรายในการใช้returnงานและมันขึ้นอยู่กับความชอบของโปรแกรมเมอร์ว่าจะใช้หรือไม่
Paul Hiemstra

38
ความเร็วในการreturnเป็นสิ่งสุดท้ายที่คุณควรกังวลเกี่ยวกับ
hadley

2
ฉันคิดว่านี่เป็นคำตอบที่ไม่ดี สาเหตุเกิดจากความขัดแย้งพื้นฐานของค่าของการใช้returnการเรียกฟังก์ชันที่ไม่จำเป็น คำถามที่คุณควรถามไม่ใช่คำถามที่คุณเสนอในตอนท้าย แต่มันเป็น“เหตุผลที่ควรผมใช้ซ้ำซ้อนreturn? มันให้ประโยชน์อะไรบ้าง” ตามที่ปรากฏคำตอบคือ "ไม่มาก" หรือแม้แต่ "ไม่มีอะไรเลย" คำตอบของคุณล้มเหลวในการชื่นชมสิ่งนี้
Konrad Rudolph

@KonradRudolph ... คุณพูดซ้ำแล้วซ้ำอีกว่าสิ่งที่พอลเดิมถาม (เหตุใดผลตอบแทนที่ชัดเจน) และฉันต้องการทราบคำตอบที่ถูกต้อง (เหมาะสำหรับทุกคน) เช่นกัน :) คุณพิจารณาที่จะให้คำอธิบายสำหรับผู้ใช้ของเว็บไซต์นี้หรือไม่?
Petr Matousu

1
@Dason ฉันได้เชื่อมโยงโพสต์ Reddit เพื่ออธิบายว่าทำไมข้อโต้แย้งนี้จึงมีข้อบกพร่องในบริบทนี้ น่าเสียดายที่ความคิดเห็นดูเหมือนว่าจะถูกลบโดยอัตโนมัติทุกครั้ง รุ่นสั้น ๆ ว่าreturnเป็นเหมือนการแสดงความคิดเห็นอย่างชัดเจนว่า“เพิ่มขึ้นโดย x 1” x = x + 2ติดกับชิ้นส่วนของรหัสทำ กล่าวอีกนัยหนึ่งคือความชัดเจน (ก) ไม่เกี่ยวข้องอย่างที่สุดและ (ข) สื่อถึงข้อมูลที่ไม่ถูกต้อง เพราะreturnความหมายของ R คือแท้จริง "ยกเลิกฟังก์ชั่นนี้" มันไม่ได้มีความหมายเหมือนกับreturnภาษาอื่น ๆ
Konrad Rudolph

103

ถ้าทุกคนยอมรับว่า

  1. return ไม่จำเป็นในตอนท้ายของร่างกายของฟังก์ชั่น
  2. ไม่ได้ใช้returnจะเร็วขึ้นเล็กน้อย (ตามการทดสอบของ @ Alan, 4.3 microseconds เทียบกับ 5.1)

เราทุกคนควรหยุดใช้returnเมื่อสิ้นสุดฟังก์ชันหรือไม่ ฉันจะไม่ทำอย่างนั้นและฉันอยากจะอธิบายว่าทำไม ฉันหวังว่าจะได้ยินถ้าคนอื่นแบ่งปันความคิดเห็นของฉัน และฉันขอโทษถ้ามันไม่ได้เป็นคำตอบที่ตรงไปยัง OP แต่เพิ่มเติมเช่นความคิดเห็นส่วนตัวยาว

ปัญหาหลักของฉันที่ไม่ได้ใช้returnคือพอลชี้ให้เห็นมีสถานที่อื่นในร่างกายของฟังก์ชั่นที่คุณอาจต้องการมัน และถ้าคุณถูกบังคับให้ใช้งานreturnที่ไหนสักแห่งในระหว่างการทำงานของคุณทำไมไม่ทำให้returnงบทั้งหมดชัดเจน? ฉันเกลียดการไม่ลงรอยกัน ฉันก็คิดว่ารหัสอ่านดีกว่า; หนึ่งสามารถสแกนฟังก์ชั่นและดูจุดออกและค่าทั้งหมดได้อย่างง่ายดาย

เปาโลใช้ตัวอย่างนี้:

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

น่าเสียดายที่คน ๆ หนึ่งอาจชี้ให้เห็นว่าสามารถเขียนใหม่ได้อย่างง่ายดายเป็น:

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

รุ่นหลังยังเป็นไปตามมาตรฐานการเขียนโปรแกรมบางอย่างที่สนับสนุนหนึ่งคำสั่งส่งคืนต่อฟังก์ชั่น ฉันคิดว่าเป็นตัวอย่างที่ดีกว่า:

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

นี่จะยากกว่ามากในการเขียนใหม่โดยใช้คำสั่ง return เดียว: มันจะต้องมีหลายbreaks และระบบที่ซับซ้อนของตัวแปรบูลีนสำหรับการเผยแพร่มัน ทั้งหมดนี้เพื่อกล่าวว่ากฎการส่งคืนเดียวเล่นได้ไม่ดีกับอาร์ดังนั้นหากคุณต้องการใช้returnในบางสถานที่ในร่างกายของฟังก์ชั่นของคุณทำไมไม่สอดคล้องกันและใช้งานได้ทุกที่?

ฉันไม่คิดว่าอาร์กิวเมนต์ความเร็วนั้นถูกต้อง ความแตกต่าง 0.8 ไมโครวินาทีคือสิ่งใดเมื่อคุณเริ่มดูฟังก์ชั่นที่ทำอะไรบางอย่าง สิ่งสุดท้ายที่ฉันเห็นคือมันพิมพ์น้อย แต่เฮ้ฉันไม่ขี้เกียจ


7
+1 มีความต้องการที่ชัดเจนสำหรับreturnคำสั่งในบางกรณีตามที่ @flodel แสดง อีกวิธีหนึ่งคือมีสถานการณ์ที่คำสั่งส่งคืนถูกละเว้นดีที่สุดเช่นการเรียกฟังก์ชันเล็ก ๆ จำนวนมากและจำนวนมาก ในอีกกรณีหนึ่งพูดว่า 95% ของกรณีที่มันไม่สำคัญว่าจะมีใครใช้returnหรือไม่และมันก็เป็นเรื่องที่ชอบ ฉันชอบใช้การส่งคืนเนื่องจากมีความชัดเจนมากขึ้นในสิ่งที่คุณหมายถึงทำให้อ่านได้ง่ายขึ้น บางทีการสนทนานี้คล้ายกับ<-vs =?
Paul Hiemstra

7
นี่คือการปฏิบัติต่อ R เป็นภาษาการเขียนโปรแกรมที่จำเป็นซึ่งไม่ใช่: เป็นภาษาการเขียนโปรแกรมที่ใช้งานได้ โปรแกรมการทำงานก็ทำงานที่แตกต่างกันและการใช้returnที่จะกลับมาเป็นค่าที่ไร้สาระในหุ้นที่มีการเขียนแทนif (x == TRUE) if (x)
Konrad Rudolph

4
นอกจากนี้คุณยังเขียนใหม่fooเป็นfoo <- function(x) if (a) a else b(ด้วยการแบ่งบรรทัดตามต้องการ) ไม่จำเป็นต้องมีค่าตอบแทนหรือค่ากลางที่ชัดเจน
hadley

26

นี่คือการสนทนาที่น่าสนใจ ฉันคิดว่าตัวอย่างของ @ flodel นั้นยอดเยี่ยม แต่ผมคิดว่ามันแสดงให้เห็นถึงจุดของฉัน (และ @koshke กล่าวนี้ในความคิดเห็น) ที่returnทำให้รู้สึกเมื่อคุณใช้ความจำเป็นแทนการทำงานรูปแบบการเข้ารหัส

ไม่ต้องปฏิเสธประเด็นนี้ แต่ฉันจะเขียนใหม่fooเช่นนี้:

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

outputสไตล์การทำงานหลีกเลี่ยงการเปลี่ยนแปลงสถานะเช่นเดียวกับการจัดเก็บค่าของ ในสไตล์นี้returnอยู่นอกสถานที่; fooดูเหมือนฟังก์ชั่นทางคณิตศาสตร์มากขึ้น

ผมเห็นด้วยกับ @flodel: การใช้ระบบซับซ้อนของตัวแปรบูลีนในจะมีความชัดเจนน้อยลงและไม่มีจุดหมายเมื่อคุณมีbar returnสิ่งที่ทำให้barคล้อยตามreturnงบก็คือมันถูกเขียนในรูปแบบที่จำเป็น แท้จริงแล้วตัวแปรบูลีนเป็นตัวแทนของการเปลี่ยนแปลง "สถานะ" ที่หลีกเลี่ยงในรูปแบบการทำงาน

มันยากมากที่จะเขียนbarในรูปแบบการใช้งานจริง ๆ เพราะมันเป็นรหัสปลอม แต่ความคิดนั้นเป็นดังนี้:

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

ห่วงจะเป็นสิ่งที่ยากที่สุดที่จะเขียนเพราะมันจะถูกควบคุมโดยรัฐเปลี่ยนแปลงไปwhilea

การสูญเสียความเร็วที่เกิดจากการโทรหาreturnมีน้อยมาก แต่ประสิทธิภาพที่ได้รับจากการหลีกเลี่ยงreturnและการเขียนใหม่ในสไตล์การใช้งานมักเป็นเรื่องใหญ่โต การบอกให้ผู้ใช้รายใหม่หยุดใช้returnอาจจะไม่ช่วย แต่การชี้แนะผู้ใช้ให้เข้ากับรูปแบบการใช้งานจะเป็นการตอบแทน


@Paul returnมีความจำเป็นในรูปแบบที่จำเป็นเพราะคุณมักต้องการออกจากฟังก์ชั่นที่จุดต่าง ๆ ในลูป returnสไตล์การทำงานไม่ได้ใช้ลูปและดังนั้นจึงไม่จำเป็นต้อง ในรูปแบบที่ใช้งานได้จริงการโทรครั้งสุดท้ายเกือบจะเป็นค่าตอบแทนที่ต้องการทุกครั้ง

ใน Python ฟังก์ชั่นต้องการreturnคำสั่ง อย่างไรก็ตามหากคุณตั้งโปรแกรมฟังก์ชั่นของคุณในสไตล์การใช้งานคุณจะมีreturnคำสั่งเพียงคำสั่งเดียว: ในตอนท้ายของฟังก์ชัน

ใช้ตัวอย่างจากโพสต์ StackOverflow อื่นให้เราบอกว่าเราต้องการฟังก์ชั่นที่คืนค่าTRUEถ้าค่าทั้งหมดในค่าที่กำหนดxมีความยาวคี่ เราสามารถใช้สองรูปแบบ:

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

ในสไตล์การใช้งานค่าที่ส่งคืนจะตกหล่นที่ส่วนท้ายของฟังก์ชันตามธรรมชาติ อีกครั้งดูเหมือนว่าฟังก์ชันทางคณิตศาสตร์

@ GSee คำเตือนที่ระบุไว้ใน?ifelseนั้นมีความน่าสนใจ แต่ฉันไม่คิดว่าพวกเขากำลังพยายามที่จะห้ามการใช้ฟังก์ชั่น ในความเป็นจริงifelseมีข้อดีของฟังก์ชั่นเวกเตอร์อัตโนมัติ ตัวอย่างเช่นพิจารณาเวอร์ชันที่แก้ไขเล็กน้อยของfoo:

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

ฟังก์ชั่นนี้ใช้งานได้ดีเมื่อlength(a)เป็น 1 แต่ถ้าคุณเขียนใหม่fooด้วยifelse

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

ตอนนี้ทำงานอยู่กับระยะเวลาของการใดfooaในความเป็นจริงมันจะทำงานได้แม้aเป็นเมทริกซ์ การคืนค่าเป็นรูปร่างเดียวกับที่testเป็นคุณลักษณะที่ช่วยในการทำให้เป็น vectorization ไม่ใช่ปัญหา


ไม่ชัดเจนสำหรับฉันว่าทำไมreturnไม่เหมาะกับรูปแบบการทำงานของการเขียนโปรแกรม เมื่อหนึ่งคือการเขียนโปรแกรมอย่างจำเป็นหรือตามหน้าที่ในบางขั้นตอนฟังก์ชั่นหรือรูทีนย่อยจำเป็นต้องส่งคืนบางสิ่งบางอย่าง ตัวอย่างเช่นการเขียนโปรแกรมการทำงานในหลามยังคงต้องมีreturnคำสั่ง คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับประเด็นนี้ได้ไหม
Paul Hiemstra

ในสถานการณ์เช่นนี้การใช้ifelse(a,a,b)เป็นสัตว์เลี้ยงโกรธของฉัน ดูเหมือนว่าทุกบรรทัดใน?ifelseนั้นจะกรีดร้องว่า "อย่าใช้ฉันแทนif (a) {a} else b" เช่น "... ส่งคืนค่าที่มีรูปร่างเดียวกับtest", "หากyesหรือnoสั้นเกินไปองค์ประกอบของมันจะถูกรีไซเคิล", "โหมดของผลลัพธ์อาจขึ้นอยู่กับค่าของtest", "แอตทริบิวต์ class ของผลลัพธ์ ถูกนำมาจากtestและอาจไม่เหมาะสมสำหรับค่าที่เลือกจากyesและno"
GSee

ในลักษณะที่สองfooไม่ได้ทำให้รู้สึกมาก bมันก็จะกลับมาจริงหรือ การใช้ifelseมันจะคืนค่า 1 หรือหลาย TRUE และ / หรือ 1 หรือหลายbs ในขั้นต้นฉันคิดว่าความตั้งใจของฟังก์ชั่นนี้คือการพูดว่า "ถ้าข้อความบางคำเป็น TRUE ให้ส่งคืนบางอย่างหรือส่งคืนอย่างอื่น" ฉันไม่คิดว่าควรจะ vectorized แล้วเพราะมันจะกลายเป็น "กลับองค์ประกอบของวัตถุบางอย่างที่เป็นจริงและสำหรับองค์ประกอบทั้งหมดที่ไม่เป็นความจริงกลับb.
GSee

22

ดูเหมือนว่าreturn()จะไม่มีเร็วกว่า ...

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

____ แก้ไข__ _ __ _ __ _ __ _ __ _ ___

ฉันดำเนินการตามมาตรฐานอื่น ๆ ( benchmark(fuu(x),foo(x),replications=1e7)) และผลลัพธ์กลับด้าน ... ฉันจะลองบนเซิร์ฟเวอร์


คุณสามารถแสดงความคิดเห็นเกี่ยวกับสาเหตุที่ทำให้เกิดความแตกต่างนี้ได้หรือไม่
พอล Hiemstra

4
คำตอบของ @PaulHiemstra Petr ครอบคลุมหนึ่งในเหตุผลหลักสำหรับเรื่องนี้ สองสายเมื่อใช้return()งานหนึ่งสายหากไม่ต้องการ มันซ้ำซ้อนกันโดยสิ้นเชิงในตอนท้ายของฟังก์ชั่นเป็นfunction()คืนค่าสุดท้าย คุณจะสังเกตเห็นเพียงแค่นี้ในฟังก์ชั่นซ้ำหลายครั้งที่ไม่ได้ทำภายในมากนักเพื่อให้ต้นทุนreturn()กลายเป็นส่วนใหญ่ของเวลาการคำนวณทั้งหมดของฟังก์ชัน
กาวินซิมป์สัน

13

ปัญหาที่ไม่ได้ใส่ 'return' อย่างชัดเจนในตอนท้ายคือถ้ามีการเพิ่มคำสั่งเพิ่มเติมในตอนท้ายของเมธอดทันใดนั้นค่าตอบแทนก็ผิด:

foo <- function() {
    dosomething()
}

dosomething()นี้จะส่งกลับค่าของ

ตอนนี้เรามาในวันถัดไปและเพิ่มบรรทัดใหม่:

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

เราต้องการให้โค้ดของเราคืนค่าdosomething()แต่กลับไม่ได้ทำอีกต่อไป

ด้วยผลตอบแทนที่ชัดเจนสิ่งนี้ชัดเจนมาก:

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

เราจะเห็นว่ามีบางอย่างแปลก ๆ เกี่ยวกับรหัสนี้และแก้ไข:

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

1
ใช่ที่จริงฉันพบว่า return ชัดเจน () มีประโยชน์เมื่อทำการดีบั๊ก; เมื่อโค้ดถูกทำความสะอาดแล้วความต้องการของมันก็น่าสนใจน้อยลงและฉันชอบความสง่างามของการไม่มีมัน ...
PatrickT

แต่นี่ไม่ใช่ปัญหาจริงในรหัสจริงมันเป็นทฤษฎีอย่างหมดจด รหัสที่อาจประสบปัญหานี้มีปัญหาที่ใหญ่กว่ามาก: การไหลของรหัสที่ไม่ชัดเจนและเปราะซึ่งไม่ชัดเจนจนทำให้การเพิ่มส่วนแบ่งง่าย ๆ
Konrad Rudolph

@ KonradRudolph ฉันคิดว่าคุณเป็นคนทำ Scotsman แบบไม่จริงกับมัน ;-) "ถ้านี่เป็นปัญหาในรหัสของคุณคุณเป็นโปรแกรมเมอร์ที่ไม่ดี!" ฉันไม่เห็นด้วยจริงๆ ฉันคิดว่าในขณะที่คุณสามารถหลีกเลี่ยงการทำโค้ดสั้น ๆ ชิ้นเล็ก ๆ ที่คุณรู้ว่าทุกบรรทัดด้วยใจมันจะกลับมากัดคุณเมื่อโค้ดของคุณใหญ่ขึ้น
Hugh Perkins

2
@HughPerkins มันไม่ได้เป็นไม่มีสกอตที่แท้จริง ; ค่อนข้างเป็นการสังเกตเชิงประจักษ์เกี่ยวกับความซับซ้อนของรหัสสำรองโดยทศวรรษของการปฏิบัติที่ดีที่สุดของวิศวกรรมซอฟต์แวร์: ทำให้ฟังก์ชั่นของแต่ละบุคคลสั้นและการไหลของรหัสชัดเจน และการละเว้นreturnไม่ใช่ทางลัด แต่เป็นรูปแบบที่เหมาะสมในการตั้งโปรแกรมการทำงาน การใช้ที่ไม่จำเป็นreturnเรียกฟังก์ชันเป็นตัวอย่างของการเขียนโปรแกรมสินค้าศาสนา
Konrad Rudolph

ดี ... ฉันไม่เห็นว่าสิ่งนี้จะป้องกันคุณจากการเพิ่มบางอย่างหลังจากreturnคำสั่งของคุณและไม่สังเกตเห็นว่ามันจะไม่ถูกดำเนินการ คุณสามารถเพิ่มความคิดเห็นหลังจากค่าที่คุณต้องการส่งคืนเช่นdosomething() # this is my return value, don't add anything after it unless you know goddam well what you are doing
lebatsnok

10

คำถามของฉันคือ: ทำไมไม่โทรreturnเร็วกว่า

มันเร็วกว่าเพราะreturnเป็นฟังก์ชั่น (ดั้งเดิม) ใน R ซึ่งหมายความว่าการใช้มันในโค้ดทำให้เกิดค่าใช้จ่ายในการเรียกใช้ฟังก์ชัน เปรียบเทียบสิ่งนี้กับภาษาการเขียนโปรแกรมส่วนใหญ่อื่น ๆ ซึ่งreturnเป็นคำหลัก แต่ไม่ใช่การเรียกใช้ฟังก์ชั่น: มันไม่ได้แปลการประมวลผลโค้ดรันไทม์ใด ๆ

ที่กล่าวว่าการเรียกฟังก์ชั่นดั้งเดิมในลักษณะนี้ค่อนข้างเร็วใน R และการเรียกใช้returnเกิดขึ้นเล็กน้อย returnนี้ไม่ได้เป็นอาร์กิวเมนต์สำหรับถนัด

หรือดีกว่าและทำให้ดีกว่า

เพราะไม่มีเหตุผล จะใช้มัน

เพราะมันซ้ำซ้อนและไม่เพิ่มความซ้ำซ้อนที่มีประโยชน์

ต้องมีความชัดเจน: ซ้ำซ้อนสามารถบางครั้งจะมีประโยชน์ แต่ความซ้ำซ้อนส่วนใหญ่ไม่ได้อยู่ในประเภทนี้ แต่เป็นชนิดที่เพิ่มความยุ่งเหยิงที่มองเห็นได้โดยไม่ต้องเพิ่มข้อมูล: มันเหมือนกับการเขียนโปรแกรมเทียบเท่ากับคำที่เติมหรือหรือแผนภูมิขยะ )

พิจารณาตัวอย่างต่อไปนี้ของความคิดเห็นที่อธิบายซึ่งได้รับการยอมรับในระดับสากลว่าเป็นความซ้ำซ้อนที่ไม่ดีเพราะความคิดเห็นนั้นเป็นเพียงการแปลรหัสที่แสดงออกมาแล้วเท่านั้น:

# Add one to the result
result = x + 1

ใช้returnใน R ตกอยู่ในประเภทเดียวกันเพราะ R คือภาษาการเขียนโปรแกรมการทำงานและใน R โทรทุกฟังก์ชั่นที่มีค่า นี่เป็นคุณสมบัติพื้นฐานของอาร์และเมื่อคุณเห็นรหัส R จากมุมมองที่ทุกการแสดงออก (รวมถึงการเรียกใช้ฟังก์ชั่นทุกครั้ง) มีค่าคำถามจะกลายเป็น:“ ทำไมฉันควรใช้return?” จำเป็นต้องมีเหตุผลในเชิงบวกเนื่องจากค่าเริ่มต้นจะไม่ใช้

หนึ่งในเหตุผลเชิงบวกดังกล่าวคือการส่งสัญญาณออกก่อนจากฟังก์ชั่นพูดในข้อยาม :

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

returnนี่คือที่ถูกต้องการใช้ที่ไม่ใช่ซ้ำซ้อนของ อย่างไรก็ตามส่วนคำสั่งการ์ดยามนั้นหายากใน R เมื่อเทียบกับภาษาอื่นและเนื่องจากทุกนิพจน์มีค่าค่าปกติifจึงไม่ต้องการreturn:

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

เราสามารถเขียนซ้ำfเช่นนี้:

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

... ที่ไหนif (cond) exprกันif (cond) expr else NULLเป็นเช่นเดียวกับ

ในที่สุดฉันต้องการคัดค้านทั้งสามข้อคัดค้าน:

  1. บางคนยืนยันว่าการใช้returnเพิ่มความชัดเจนเพราะมันเป็นสัญญาณว่า "ฟังก์ชั่นนี้คืนค่า" แต่ตามที่อธิบายไว้ข้างต้นทุกสิ่งที่กลับมาทำงานในอาร์คิดถึงreturnเป็นเครื่องหมายของการกลับมาเป็นค่าที่ไม่ได้เป็นเพียงแค่ซ้ำซ้อนก็แข็งขันทำให้เข้าใจผิด

  2. ที่เกี่ยวข้องZen of Pythonมีแนวทางที่น่าอัศจรรย์ที่ควรปฏิบัติตาม:

    ชัดเจนดีกว่าโดยปริยาย

    การทิ้งซ้ำซ้อนreturnไม่ละเมิดสิ่งนี้อย่างไร เนื่องจากค่าส่งคืนของฟังก์ชันในภาษาที่ใช้งานฟังก์ชั่นนั้นชัดเจนเสมอนั่นคือเป็นนิพจน์สุดท้าย นี่เป็นอาร์กิวเมนต์เดียวกันอีกครั้งเกี่ยวกับความชัดเจน vs redundancy

    ในความเป็นจริงหากคุณต้องการ explicitness ให้ใช้เพื่อเน้นข้อยกเว้นกฎ: ทำเครื่องหมายฟังก์ชันที่ไม่ส่งคืนค่าที่มีความหมายซึ่งถูกเรียกใช้สำหรับผลข้างเคียงเท่านั้น (เช่นcat) ยกเว้น R มีเครื่องหมายที่ดีกว่าสำหรับกรณีนี้:return invisibleเช่นฉันจะเขียน

    save_results = function (results, file) {
        # … code that writes the results to a file …
        invisible()
    }
  3. แต่ฟังก์ชั่นยาว ๆ จะเป็นอย่างไร? จะไม่ง่ายต่อการสูญเสียการติดตามสิ่งที่จะถูกส่งกลับ

    สองคำตอบ: ก่อนอื่นไม่ได้จริงๆ กฎชัดเจน: การแสดงออกครั้งสุดท้ายของฟังก์ชั่นคือมูลค่าของมัน ไม่มีอะไรให้ติดตาม

    แต่ที่สำคัญกว่านั้นปัญหาในฟังก์ชั่นที่ยาวนานไม่ใช่การขาดreturnเครื่องหมายที่ชัดเจน มันเป็นความยาวของฟังก์ชั่น ฟังก์ชั่นที่ยาวนานเกือบ (?) มักจะละเมิดหลักการความรับผิดชอบเดียวเสมอและแม้ว่าพวกเขาจะไม่ได้รับประโยชน์พวกเขาก็จะได้รับประโยชน์จากการถูกแยกออกจากกันเพื่อให้สามารถอ่านได้


บางทีฉันควรเพิ่มว่าบางคนสนับสนุนให้ใช้returnเพื่อให้คล้ายกับภาษาอื่น ๆ แต่นั่นเป็นข้อโต้แย้งที่ไม่ดี: ภาษาการเขียนโปรแกรมการทำงานอื่น ๆ มักจะไม่ใช้returnเช่นกัน มันเป็นแค่ภาษาที่จำเป็นเท่านั้นที่ไม่ใช่ทุกการแสดงออกที่มีค่า
Konrad Rudolph

ฉันมาถึงคำถามนี้พร้อมกับความเห็นที่ว่าการใช้การreturnสนับสนุนดีกว่าอย่างชัดเจนและอ่านคำตอบของคุณด้วยการวิจารณ์อย่างสมบูรณ์ คำตอบของคุณทำให้ฉันนึกถึงมุมมองนั้น ฉันคิดว่าจำเป็นต้องใช้returnสำหรับอย่างชัดเจน (อย่างน้อยในกรณีของฉัน) ถูกผูกไว้กับความต้องการที่จะสามารถปรับปรุงการทำงานของฉันได้ดีขึ้นในเวลาต่อมา returnด้วยความคิดที่ว่าฟังก์ชั่นของฉันเพียงแค่อาจจะซับซ้อนเกินไปตอนนี้ผมจะเห็นว่าเป้าหมายสำหรับการปรับปรุงรูปแบบการเขียนโปรแกรมของฉันจะมุ่งมั่นในการจัดโครงสร้างรหัสในการรักษาอย่างชัดเจนโดยไม่ต้อง ขอบคุณสำหรับภาพสะท้อนและความเข้าใจเหล่านั้น !!
Kasper Thystrup Karstensen

6

ฉันคิดว่าreturnเป็นเคล็ดลับ ตามกฎทั่วไปค่าของนิพจน์สุดท้ายที่ประเมินในฟังก์ชันจะกลายเป็นค่าของฟังก์ชัน - และรูปแบบทั่วไปนี้พบได้ในหลายที่ ทั้งหมดต่อไปนี้ประเมินเป็น 3:

local({
1
2
3
})

eval(expression({
1
2
3
}))

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

สิ่งที่returnไม่ได้คืนค่าจริง ๆ(นี้จะมีหรือไม่มีมัน) แต่ "แตกออก" ของฟังก์ชั่นในทางที่ผิดปกติ ในแง่นั้นมันเป็นคำที่ใกล้เคียงที่สุดของคำสั่ง GOTO ใน R (นอกจากนี้ยังมีตัวแบ่งและถัดไป) ฉันใช้returnน้อยมากและไม่เคยสิ้นสุดฟังก์ชั่น

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

... สิ่งนี้สามารถเขียนใหม่ได้if(a) a else bซึ่งสามารถอ่านได้ดีกว่าและมีความโค้งน้อยกว่ามาก ไม่ต้องการreturnเลย กรณีต้นแบบของฉันในการใช้ "return" จะเป็นสิ่งที่ ...

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

โดยทั่วไปความต้องการการกลับมาจำนวนมากชี้ให้เห็นว่าปัญหานั้นมีโครงสร้างที่น่าเกลียดหรือไม่ดี

<>

return ไม่จำเป็นต้องใช้ฟังก์ชั่นในการทำงานจริงๆ: คุณสามารถใช้มันเพื่อแยกชุดของนิพจน์ที่จะประเมินผล

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)

วันนี้ฉันพบกรณีที่อาจต้องการจริง ๆreturn(ตัวอย่างน่าเกลียดของฉันด้านบนเป็นเทียมสูง): สมมติว่าคุณจำเป็นต้องทดสอบว่าค่าเป็นNULLหรือNA: ในกรณีเหล่านี้กลับสตริงว่างเปล่ามิฉะนั้นคืนcharacterค่า แต่การทดสอบis.na(NULL)ทำให้เกิดข้อผิดพลาดดังนั้นจึงดูเหมือนว่าจะทำได้ด้วยการif(is.null(x)) return("")ดำเนินการต่อif(is.na(x)) .....เท่านั้น (หนึ่งสามารถใช้length(x)==0แทนis.null(x)แต่ยังคงเป็นไปไม่ได้ที่จะใช้length(x)==0 | is.na(x)ถ้าxเป็นNULL.)
lebatsnok

1
นั่นเป็นเพราะคุณใช้|(vectorised OR ที่ประเมินทั้งสองด้าน) แทน||(ลัดวงจรหรือไม่ใช่เวกเตอร์ที่มีการประเมินภาคแสดง) พิจารณาif (TRUE | stop()) print(1)กับif (TRUE || stop()) print(1)
asac

2

return สามารถเพิ่มการอ่านรหัสได้:

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

3
บางทีมันอาจเป็นไปได้ แต่มันไม่ได้ทำในตัวอย่างของคุณ แต่จะปิดบัง (หรือค่อนข้างซับซ้อน) การไหลของรหัส
Konrad Rudolph

1
ฟังก์ชั่นของคุณสามารถทำให้ง่ายขึ้น: foo <- function() a || b(ซึ่งเป็น IMO อ่านง่ายขึ้นในกรณีใด ๆ ไม่มีการอ่าน "บริสุทธิ์" แต่อ่านง่ายในความเห็นของใครบางคน: มีคนที่พูดภาษาแอสเซมบลีอ่านได้อย่างสมบูรณ์)
lebatsnok

1

อาร์กิวเมนต์ของความซ้ำซ้อนเกิดขึ้นมากมายที่นี่ ในความคิดของฉันที่ไม่สมเหตุสมผลพอที่จะละเว้นreturn()ในความคิดของฉันที่ไม่ได้เป็นเหตุผลเพียงพอที่จะงดความซ้ำซ้อนไม่ได้เป็นสิ่งที่เลวร้ายโดยอัตโนมัติ เมื่อใช้อย่างมีกลยุทธ์ความซ้ำซ้อนทำให้โค้ดชัดเจนและบำรุงรักษาได้มากขึ้น

ลองพิจารณาตัวอย่างนี้: พารามิเตอร์ฟังก์ชั่นมักจะมีค่าเริ่มต้น ดังนั้นการระบุค่าที่เหมือนกับค่าเริ่มต้นคือซ้ำซ้อน นอกจากจะทำให้เห็นได้ชัดว่าพฤติกรรมที่ฉันคาดหวัง ไม่จำเป็นต้องดึง manpage ของฟังก์ชั่นขึ้นมาเพื่อเตือนตัวเองว่าค่าเริ่มต้นคืออะไร และไม่ต้องกังวลกับรุ่นในอนาคตของฟังก์ชั่นการเปลี่ยนค่าเริ่มต้น

ด้วยโทษประสิทธิภาพเล็กน้อยสำหรับการโทรreturn()(ตามมาตรฐานที่โพสต์ที่นี่โดยผู้อื่น) มันลงมาในรูปแบบมากกว่าถูกและผิด เพื่อให้สิ่งที่ "ผิด" จำเป็นต้องมีข้อเสียที่ชัดเจนและไม่มีใครที่นี่แสดงให้เห็นว่าเป็นที่น่าพอใจว่าการรวมหรือการละเว้นreturn()มีข้อเสียที่สอดคล้องกัน ดูเหมือนว่าเฉพาะกรณีและผู้ใช้เฉพาะ

ดังนั้นนี่คือที่ฉันยืนอยู่บนนี้

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

ฉันอึดอัดกับตัวแปร "เด็กกำพร้า" เหมือนในตัวอย่างด้านบน ได้รับabcdจะเป็นส่วนหนึ่งของคำสั่งฉันยังพูดไม่จบเขียน? มันเป็นเศษเล็กเศษน้อยของรอยต่อ / แก้ไขในรหัสของฉันและจำเป็นต้องลบหรือไม่ ฉันตั้งใจวาง / ย้ายบางสิ่งบางอย่างจากที่อื่นหรือไม่?

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

ในทางตรงกันข้ามตัวอย่างที่สองนี้ทำให้ฉันเห็นได้ชัดว่าเป็นค่าส่งคืนที่ตั้งใจไว้แทนที่จะเป็นอุบัติเหตุหรือรหัสที่ไม่สมบูรณ์ สำหรับฉันความซ้ำซ้อนนี้ไม่ไร้ประโยชน์อย่างแน่นอน

แน่นอนเมื่อฟังก์ชั่นเสร็จสิ้นและทำงานฉันสามารถลบผลตอบแทน แต่การลบมันอยู่ในตัวเองเป็นขั้นตอนพิเศษที่ซ้ำซ้อนและในมุมมองของฉันไร้ประโยชน์มากกว่ารวมถึงreturn()ในสถานที่แรก

ทั้งหมดที่กล่าวว่าฉันไม่ได้ใช้return()ในฟังก์ชั่นหนึ่งซับสั้นที่ไม่มีชื่อ มันประกอบขึ้นเป็นส่วนใหญ่ของรหัสฟังก์ชั่นและส่วนใหญ่ทำให้เกิดความยุ่งเหยิงภาพที่ทำให้รหัสชัดเจนน้อย แต่สำหรับฟังก์ชั่นที่กำหนดไว้อย่างเป็นทางการและมีชื่อมากขึ้นฉันใช้มันและมีแนวโน้มที่จะดำเนินการต่อไป


“ abcd จะเป็นส่วนหนึ่งของคำแถลงที่ฉันเขียนไม่เสร็จใช่ไหม” - สิ่งนี้แตกต่างจากนิพจน์อื่น ๆ ที่คุณเขียนอย่างไร ฉันคิดว่านี่เป็นแก่นแท้ของความไม่เห็นด้วยของเรา การมีตัวแปรยืนอยู่บนตัวของมันเองอาจเป็นเรื่องแปลกในภาษาการเขียนโปรแกรมที่จำเป็น แต่มันเป็นเรื่องปกติและคาดว่าจะเป็นภาษาการเขียนโปรแกรมที่ใช้งานได้ ฉันอ้างว่าเป็นปัญหาเพียงว่าคุณไม่คุ้นเคยกับการเขียนโปรแกรมที่ใช้งานได้ (ความจริงที่ว่าคุณพูดถึง "คำสั่ง" แทนที่จะเป็น "การแสดงออก" เป็นการเสริมสิ่งนี้)
Konrad Rudolph

มันแตกต่างกันเนื่องจากคำสั่งอื่น ๆ มักจะทำอะไรบางอย่างในวิธีที่ชัดเจนกว่า: มันคือการมอบหมายการเปรียบเทียบการเรียกใช้ฟังก์ชั่น ... ใช่ขั้นตอนการเข้ารหัสครั้งแรกของฉันเป็นภาษาที่จำเป็นและฉันยังคงใช้ภาษาที่จำเป็น การมีตัวชี้นำภาพเหมือนกันในทุกภาษา (ทุกที่ที่ภาษาอนุญาต) ทำให้งานของฉันง่ายขึ้น A return()ใน R ไม่มีค่าใช้จ่าย มันซ้ำซ้อนอย่างเป็นกลาง แต่การ "ไร้ประโยชน์" คือการตัดสินตามอัตวิสัยของคุณ ซ้ำซ้อนและไร้ประโยชน์ไม่จำเป็นต้องมีความหมายเหมือนกัน นั่นคือสิ่งที่เราไม่เห็นด้วย
cymon

นอกจากนี้ฉันไม่ใช่วิศวกรซอฟต์แวร์หรือนักวิทยาศาสตร์คอมพิวเตอร์ อย่าอ่านความแตกต่างเล็กน้อยเกินไปในการใช้คำศัพท์ของฉัน
cymon

เพียงเพื่อชี้แจง:“ ซ้ำซ้อนและไร้ประโยชน์ไม่จำเป็นต้องมีความหมายเหมือนกัน นั่นคือสิ่งที่เราไม่เห็นด้วย” - ไม่ฉันเห็นด้วยอย่างยิ่งกับสิ่งนั้นและฉันได้ทำประเด็นนั้นอย่างชัดเจนในคำตอบของฉัน ซ้ำซ้อนจะมีประโยชน์หรือแม้กระทั่งสิ่งสำคัญ แต่สิ่งนี้จะต้องมีการแสดงอย่างแข็งขันไม่ใช่ข้อสันนิษฐาน ฉันเข้าใจเหตุผลของคุณว่าทำไมคุณถึงคิดว่าเรื่องนี้มีไว้returnและแม้ว่าฉันจะไม่เชื่อก็ตามฉันคิดว่ามันอาจเป็นไปได้ (แน่นอนเป็นภาษาที่จำเป็น ... ความเชื่อของฉันคือ
Konrad Rudolph
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.