Scala for Backend Engineering & Functional Programming · บทเรียน

แอนโนเทชัน tailrec

รับประกันการปรับปรุงประสิทธิภาพ

บทเรียน 2 จาก 413 ขั้นตอน

แอนโนเทชัน tailrec เป็นบทเรียน Scala for Backend Engineering & Functional Programming ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Scala for Backend Engineering & Functional Programming และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Scala for Backend Engineering & Functional Programming มีบทเรียนทั้งหมด 4 บทเรียน

การเรียกซ้ำแบบหางคืออะไร

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

ตำแหน่งหาง

ใน n * factorial(n-1) การเรียกซ้ำ ไม่ใช่ การกระทำสุดท้าย เพราะการคูณจะเกิดขึ้นหลังจากฟังก์ชันส่งค่ากลับ ส่วนใน gcd(b, a % b) การเรียกนั้น เป็น การกระทำสุดท้าย ดังนั้นเฉพาะกรณีหลังจึงเป็นการเรียกซ้ำแบบหาง

คำอธิบายประกอบ @tailrec

นำเข้า scala.annotation.tailrec และใส่คำอธิบายประกอบให้กับเมธอด จากนั้นคอมไพเลอร์จะ ตรวจสอบ ว่าการเรียกนั้นอยู่ในตำแหน่งหางจริงหรือไม่ และใช้การปรับปรุงประสิทธิภาพ หากไม่ใช่ การคอมไพล์จะล้มเหลว

import scala.annotation.tailrec

object Main {
  @tailrec
  def countdown(n: Int): Unit = {
    if (n >= 0) {
      println(n)
      countdown(n - 1)
    }
  }

  def main(args: Array[String]): Unit = countdown(3)
}

การปรับปรุงที่รับประกันได้

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

gcd แบบเรียกซ้ำแบบหาง

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

import scala.annotation.tailrec

object Main {
  @tailrec
  def gcd(a: Int, b: Int): Int =
    if (b == 0) a else gcd(b, a % b)

  def main(args: Array[String]): Unit = {
    println(gcd(1071, 462))
  }
}

สิ่งที่ทำให้หลุดจากตำแหน่งหาง

รูปแบบทั่วไปที่ทำให้การเรียกหลุดจากตำแหน่งหาง:

  • คำนวณทางคณิตศาสตร์กับผลลัพธ์: n + f(...)
  • ห่อด้วยตัวสร้าง: x :: f(...)
  • ใช้ผลลัพธ์ภายในบล็อก try

ตัวอย่างที่ไม่ใช่การเรียกซ้ำแบบหาง

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

object Main {
  def sum(n: Int): Int =
    if (n == 0) 0
    else n + sum(n - 1)

  def main(args: Array[String]): Unit = {
    println(sum(100))
  }
}

เหตุใดจึงปรับปรุงไม่ได้

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

ลูปขนาดใหญ่แบบเรียกซ้ำแบบหาง

ผลรวมแบบเรียกซ้ำแบบหางที่ใช้ตัวสะสมสามารถทำงานกับข้อมูลเข้าขนาดใหญ่มากได้โดยไม่ทำให้สแตกเต็ม เพราะใช้เฟรมเดิมซ้ำ

import scala.annotation.tailrec

object Main {
  @tailrec
  def sumTo(n: Int, acc: Long = 0): Long =
    if (n == 0) acc else sumTo(n - 1, acc + n)

  def main(args: Array[String]): Unit = {
    println(sumTo(1000000))
  }
}

tailrec ต้องเป็น final หรือเมธอดภายใน

เพื่อให้ @tailrec ทำงานได้ เมธอดต้องไม่สามารถถูกเขียนทับได้ กล่าวคือต้องเป็น private, final หรือเป็นเมธอดภายใน/เมธอดซ้อน เมธอดที่เปิดให้เขียนทับอาจถูกแทนที่และทำให้การปรับปรุงประสิทธิภาพใช้ไม่ได้ ดังนั้นคอมไพเลอร์จึงปฏิเสธเมธอดนั้น

ข้อควรระวังของการเรียกซึ่งกันและกัน

@tailrec ปรับปรุงได้เฉพาะฟังก์ชันที่เรียก ตัวเอง เท่านั้น ฟังก์ชันสองฟังก์ชันที่เรียกหากันเอง (การเรียกซึ่งกันและกัน) ไม่สามารถปรับเป็นการเรียกแบบหางโดย JVM ได้โดยตรง กรณีนี้ต้องใช้การทำแทรมโพลีน ซึ่งจะกล่าวถึงในภายหลัง

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ทดสอบความเข้าใจของคุณเกี่ยวกับ @tailrec

สรุปทบทวน

คุณได้เรียนรู้เรื่อง คำอธิบายประกอบ @tailrec:

  • การเรียกใน ตำแหน่งหาง สามารถปรับให้เป็นลูปได้
  • @tailrec รับประกันความปลอดภัยต่อสแตกขณะคอมไพล์
  • เมธอดต้องเป็น final, private หรือเป็นเมธอดภายใน
  • รองรับเฉพาะการเรียกตัวเอง ไม่รองรับการเรียกซึ่งกันและกัน
เริ่มต้นได้ฟรี

เรียนรู้ Scala ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
39
บทเรียน
143

คำถามที่พบบ่อย

บทเรียน “แอนโนเทชัน tailrec” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “แอนโนเทชัน tailrec” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Scala for Backend Engineering & Functional Programming ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Scala for Backend Engineering & Functional Programming มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “แอนโนเทชัน tailrec”

รับประกันการปรับปรุงประสิทธิภาพ คุณปฏิบัติ Scala for Backend Engineering & Functional Programming ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Scala for Backend Engineering & Functional Programming หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Scala for Backend Engineering & Functional Programming บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “แอนโนเทชัน tailrec” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Scala for Backend Engineering & Functional Programming นี้ได้ไหม

ได้ บทเรียน Scala for Backend Engineering & Functional Programming ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. พื้นฐานการเรียกซ้ำ
  2. แอนโนเทชัน tailrec
  3. รูปแบบตัวสะสม
  4. แทรมโพลีน
← กลับไปที่ Scala for Backend Engineering & Functional Programming