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

แทรมโพลีน

การเรียกซ้ำที่ปลอดภัยต่อสแตก

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

ข้อจำกัดของ @tailrec

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

ปัญหาการเรียกซึ่งกันและกัน

พิจารณา isEven และ isOdd ที่นิยามโดยอาศัยกันและกัน เมื่อใช้กับตัวเลขขนาดใหญ่ การทำงานจะทำให้สแตกเต็ม และไม่สามารถใส่ @tailrec ให้ฟังก์ชันใดได้

object Main {
  def isEven(n: Int): Boolean = if (n == 0) true else isOdd(n - 1)
  def isOdd(n: Int): Boolean  = if (n == 0) false else isEven(n - 1)

  def main(args: Array[String]): Unit = {
    println(isEven(10))
  }
}

แทรมโพลีนคืออะไร

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

TailRec ในไลบรารีมาตรฐาน

Scala มี scala.util.control.TailCalls พร้อมชนิด TailRec ใช้ done(x) สำหรับผลลัพธ์สุดท้าย และใช้ tailcall(...) เพื่อเลื่อนการเรียกถัดไปออกไป

import scala.util.control.TailCalls._

object Main {
  def isEven(n: Int): TailRec[Boolean] =
    if (n == 0) done(true) else tailcall(isOdd(n - 1))
  def isOdd(n: Int): TailRec[Boolean] =
    if (n == 0) done(false) else tailcall(isEven(n - 1))

  def main(args: Array[String]): Unit = {
    println(isEven(100000).result)
  }
}

done และ tailcall

องค์ประกอบพื้นฐานสองอย่าง:

  • done(value) ห่อคำตอบสุดท้าย
  • tailcall(expr) เลื่อนการเรียกที่ส่งคืนค่าเป็น TailRec ออกไป

การเรียก .result จะทำงานลูปแทรมโพลีนและสร้างค่าออกมา

ความปลอดภัยต่อสแตก

เนื่องจาก tailcall แต่ละครั้งส่งการควบคุมกลับไปยังลูปควบคุมแทนการซ้อนการเรียก Java สแตกของ JVM จึงไม่เติบโตตามความลึกของการเรียกซ้ำ ตัวอย่างข้างต้นรองรับ 100,000 ขั้นตอนได้โดยไม่ทำให้สแตกเต็ม

การทำแทรมโพลีนกับการเรียกตัวเอง

แทรมโพลีนยังใช้กับการเรียกตัวเองแบบลึกทั่วไปได้ เมื่อไม่สามารถใช้ตัวสะสมได้สะดวก ในที่นี้การนับถอยหลังที่ลึกยังคงปลอดภัยต่อสแตก

import scala.util.control.TailCalls._

object Main {
  def countDown(n: Int): TailRec[Int] =
    if (n == 0) done(0) else tailcall(countDown(n - 1))

  def main(args: Array[String]): Unit = {
    println(countDown(500000).result)
  }
}

การรวมผลลัพธ์ด้วย flatMap

TailRec รองรับ map และ flatMap คุณจึงทำงานต่อหลังการเรียกที่เลื่อนออกไปได้ โดยยังคงปลอดภัยต่อสแตก

import scala.util.control.TailCalls._

object Main {
  def sum(n: Int): TailRec[Int] =
    if (n == 0) done(0)
    else tailcall(sum(n - 1)).map(_ + n)

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

ลูปควบคุมทำงานอย่างไร

ในเชิงแนวคิด .result จะทำงานเป็นลูป: นำขั้นตอนปัจจุบันมา หากเป็น done ก็ส่งค่ากลับ หากเป็นการเรียกที่เลื่อนออกไป ก็ประเมินหนึ่งชั้นแล้วทำต่อ ทั้งหมดนี้ใช้พื้นที่สแตกคงที่

การทำแทรมโพลีนในไลบรารีเอฟเฟกต์

ไลบรารีอย่าง Cats Effect และ ZIO จะทำแทรมโพลีนให้สาย flatMap ภายในไลบรารี นี่จึงเป็นเหตุผลที่คุณสามารถสร้างโปรแกรมเอฟเฟกต์ที่ซ้อนกันลึกมากได้โดยไม่ทำให้สแตกเต็ม การทำแทรมโพลีนเป็นรากฐานของเอฟเฟกต์เชิงฟังก์ชันที่ปลอดภัยต่อสแตก

ควรใช้แทรมโพลีนเมื่อใด

ใช้การเรียกต่อแบบแทรมโพลีนเมื่อ:

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

สำหรับการเรียกซ้ำตัวเองแบบง่าย ให้เลือกใช้ @tailrec ร่วมกับตัวสะสมก่อน

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

ทดสอบความเข้าใจเกี่ยวกับการเรียกต่อแบบแทรมโพลีน

สรุปทบทวน

คุณได้เรียนรู้เรื่องการเรียกต่อแบบแทรมโพลีน:

  • ช่วยให้การเรียกซ้ำซึ่งกันและกันและการเรียกซ้ำที่ลึกมากปลอดภัยต่อสแตก
  • ใช้ TailCalls: done(x) และ tailcall(...) จากนั้นใช้ .result
  • TailRec รองรับ map/flatMap
  • เป็นพื้นฐานของไลบรารีเอฟเฟกต์ที่ปลอดภัยต่อสแตก

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

บทเรียน “แทรมโพลีน” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “แทรมโพลีน”

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

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

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

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

บทเรียน 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