0Pricing
Scala for Backend Engineering & Functional Programming · درس

أنماط المُجمِّع

مرّر الحالة عبر الاستدعاء递归ي

أنماط المُجمِّع درس مجاني في Scala for Backend Engineering & Functional Programming على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Scala for Backend Engineering & Functional Programming، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Scala for Backend Engineering & Functional Programming 4 دروس في المجموع.

بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.

Why Accumulators

Plain recursion builds its result on the way back up the call stack, after the recursive call returns.

An accumulator carries a running result down into each call instead, so the answer is ready when the base case is reached.

This small shift unlocks tail recursion and constant stack usage.

The Helper Function

The accumulator pattern uses an inner helper that takes an extra parameter: the result so far.

The outer function just kicks it off with a starting value, often 0 or an empty list.

def sum(xs: List[Int]): Int = {
  def loop(rest: List[Int], acc: Int): Int = rest match {
    case Nil    => acc
    case h :: t => loop(t, acc + h)
  }
  loop(xs, 0)
}

Running the Accumulator

Here the full program sums a list using an accumulator.

Notice the base case returns acc directly, not 0. The total has been built up as we descended through the list.

def sum(xs: List[Int]): Int = {
  def loop(rest: List[Int], acc: Int): Int = rest match {
    case Nil    => acc
    case h :: t => loop(t, acc + h)
  }
  loop(xs, 0)
}

@main def run(): Unit =
  println(sum(List(1, 2, 3, 4)))  // 10

Comparing the Two Shapes

In plain recursion the combine step (h + ...) waits for the inner call.

In the accumulator version the combine happens before the call, and the call is the very last thing the function does.

That last-call property is what makes it tail recursive.

// Plain: combine after the call
case h :: t => h + sum(t)

// Accumulator: combine before the call
case h :: t => loop(t, acc + h)

Tail Recursion

A tail-recursive call is one where the recursive call is the function's final action, with nothing left to do afterward.

Scala can optimize this into a loop, reusing a single stack frame, so it never overflows no matter how deep.

import scala.annotation.tailrec

@tailrec
def countDown(n: Int): Unit =
  if (n < 0) ()
  else { println(n); countDown(n - 1) }

The @tailrec Annotation

Adding @tailrec asks the compiler to verify the function really is tail recursive.

If it is not, compilation fails with a clear error. This turns a silent performance trap into a build-time guarantee.

import scala.annotation.tailrec

def sum(xs: List[Int]): Int = {
  @tailrec
  def loop(rest: List[Int], acc: Int): Int = rest match {
    case Nil    => acc
    case h :: t => loop(t, acc + h)
  }
  loop(xs, 0)
}

@main def run(): Unit = println(sum((1 to 100000).toList))

Accumulating a List

Accumulators do not have to hold numbers. They can build collections too.

This reverse function prepends each head onto the accumulator, which naturally flips the order. Prepending with :: is fast, so this is efficient.

def reverse[A](xs: List[A]): List[A] = {
  def loop(rest: List[A], acc: List[A]): List[A] = rest match {
    case Nil    => acc
    case h :: t => loop(t, h :: acc)
  }
  loop(xs, Nil)
}

Reverse in Action

The accumulator starts empty and grows as we consume the input.

Because each head is pushed onto the front of acc, the first element ends up last, giving a reversed list at constant stack cost.

def reverse[A](xs: List[A]): List[A] = {
  def loop(rest: List[A], acc: List[A]): List[A] = rest match {
    case Nil    => acc
    case h :: t => loop(t, h :: acc)
  }
  loop(xs, Nil)
}

@main def run(): Unit =
  println(reverse(List(1, 2, 3)))  // List(3, 2, 1)

Multiple Accumulators

A helper can carry several accumulators at once.

Here we track a running product and a count in the same loop, returning both as a tuple.

Each one threads its updated value into the next call.

def stats(xs: List[Int]): (Int, Int) = {
  def loop(rest: List[Int], prod: Int, count: Int): (Int, Int) =
    rest match {
      case Nil    => (prod, count)
      case h :: t => loop(t, prod * h, count + 1)
    }
  loop(xs, 1, 0)
}

Choosing the Initial Value

The starting accumulator must be the identity for your operation.

For addition use 0, for multiplication use 1, for list building use Nil, for string joining use the empty string.

A wrong seed quietly produces wrong answers.

// addition  -> seed 0
// product   -> seed 1
// list      -> seed Nil
// string    -> seed ""

Order of Results

Accumulator recursion processes elements left to right, but a prepend-based accumulator reverses them.

If you need to preserve order while building a list, either reverse at the end or append, though appending is slower. Prepend-then-reverse is the usual idiom.

def mapInc(xs: List[Int]): List[Int] = {
  def loop(rest: List[Int], acc: List[Int]): List[Int] = rest match {
    case Nil    => acc.reverse
    case h :: t => loop(t, (h + 1) :: acc)
  }
  loop(xs, Nil)
}

Quick Check

Pick the accurate statement about accumulator recursion.

Recap

An accumulator threads the running result down through recursive calls, so the base case can return it directly.

This makes the recursive call sit in tail position, enabling Scala's tail-call optimization and @tailrec safety check.

Seed the accumulator with the operation's identity value, and reverse at the end when order matters.

الأسئلة الشائعة

هل درس «أنماط المُجمِّع» مجاني؟

نعم — نص درس «أنماط المُجمِّع» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Scala for Backend Engineering & Functional Programming، انتقل إلى CoddyKit PRO. تتضمن دورة Scala for Backend Engineering & Functional Programming 4 دروس في المجموع.

ماذا ستتعلم في «أنماط المُجمِّع»؟

مرّر الحالة عبر الاستدعاء递归ي تتمرن على Scala for Backend Engineering & Functional Programming مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Scala for Backend Engineering & Functional Programming؟

لا تُشترط خبرة سابقة. Scala for Backend Engineering & Functional Programming على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «أنماط المُجمِّع»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Scala for Backend Engineering & Functional Programming هذا؟

نعم. كل درس في Scala for Backend Engineering & Functional Programming يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. التفكير递归يًا
  2. أنماط المُجمِّع
  3. foldLeft وfoldRight
  4. reduce وaggregate
← العودة إلى Scala for Backend Engineering & Functional Programming