recursion iteration and tail calls in js - Lee-hyuna/33-js-concepts-kr GitHub Wiki

JS의 μž¬κ·€, 반볡 그리고 꼬리 호좜 (tail call)

원문: Recursion, iteration and tail calls in JS

μž¬κ·€μ˜ κ°€μž₯ ν‘œμ€€ μ˜ˆλŠ” 주어진 숫자λ₯Ό 1λΆ€ν„° nκΉŒμ§€ λͺ¨λ‘ κ³±ν•˜λŠ” n! = n * (n - 1) * ... * 1 νŒ©ν† λ¦¬μ–Ό ν•¨μˆ˜ 이닀.

function factorial(n) {
    if (n === 0) {
        return 1;
    }
    return n * factorial(n - 1);
}

μœ„μ— λ‚˜νƒ€λ‚Έ μ˜ˆμ œλŠ” νŒ©ν† λ¦¬μ–Ό ν•¨μˆ˜μ˜ κ°€μž₯ ν‰λ²”ν•œ κ΅¬ν˜„μ΄λ‹€.

n = 6일 λ•Œ μœ„μ˜ μ˜ˆμ œκ°€ μ–΄λ–»κ²Œ λ™μž‘ν•˜λŠ”μ§€ μ‚΄νŽ΄λ³΄μž,

  • factorial(6)
    • 6 * factorial(5)
      • 5 * factorial (4)
        • 4 * factorial(3)
          • 3 * factorial(2)
            • 2 * factorial(1)
              • 1 * factorial(0)
                • 1
              • (resuming previous execution) 1 * 1 = 1
            • (resuming…) 2 * 1 = 2
          • (…) 3 * 2 = 6
        • … 4 * 6 = 24
      • 5 * 24 = 120
    • 6 * 120 = 720
  • factorial(6) = 720

μ•žμœΌλ‘œ 일어날 μž‘μ—…λ“€μ„ 이해할 수 μžˆλ„λ‘ 무슨 일이 μΌμ–΄λ‚˜κ³  μžˆλŠ”μ§€ μ‹ μ€‘ν•˜κ²Œ λ΄μ•Όν•œλ‹€.

ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λ©΄ ν•œ λ²ˆμ— μ—¬λŸ¬ 가지 일이 λ°œμƒν•œλ‹€. ν•¨μˆ˜λ₯Ό 호좜 ν•œ ν›„ λŒμ•„ κ°€μ•Όν•˜λŠ” μœ„μΉ˜λŠ” ν˜„μž¬ ν”„λ ˆμž„(콜 μŠ€νƒμ˜)의 정보 (예 : n κ°’)와 ν•¨κ»˜ μ €μž₯λ©λ‹ˆλ‹€. 그런 λ‹€μŒ μƒˆ ν•¨μˆ˜λ₯Ό μœ„ν•œ 곡간이 ν• λ‹Ήλ˜κ³  μƒˆ ν”„λ ˆμž„μ΄ μƒμ„±λœλ‹€.

κ³„μ†ν•΄μ„œ ν”„λ ˆμž„μ„ μŒ“μ•„ 올린 λ‹€μŒ ν•΄λ‹Ή μŠ€νƒμ„ ν’€μ–΄ ν•¨μˆ˜ ν˜ΈμΆœμ„ λ°˜ν™˜ 된 κ°’μœΌλ‘œ λ°”κΎΌλ‹€.

μ£Όλͺ©ν•΄μ•Ό ν•  또 λ‹€λ₯Έ 사항은 ν•¨μˆ˜μ— μ˜ν•΄ 생성 된 ν”„λ‘œμ„ΈμŠ€μ˜ λͺ¨μ–‘이닀. 이 λͺ¨μ–‘을 μž¬κ·€ 라고 λΆ€λ₯΄λ©΄ 놀라지 μ•Šμ„ 것 이닀. λ”°λΌμ„œ μž¬κ·€ ν”„λ‘œμ„ΈμŠ€κ°€ μžˆλ‹€. λ­”μ†Œλ¦¬μ—¬

νŒ©ν† λ¦¬μ–Όμ˜ 두 번째 κ΅¬ν˜„μ„ μ‚΄νŽ΄λ³΄μž.

function factorial(n, res) {
    if (n === 0) {
        return res;
    }
    return factorial(n - 1, res * n);
}

λ‚΄λΆ€ ν•¨μˆ˜λ₯Ό μ •μ˜ν•¨μœΌλ‘œμ¨ ν•¨μˆ˜λ₯Ό μ’€ 더 μΊ‘μŠν™”ν•  수 μžˆλ‹€.

function factorial(n) {
    function inner_factorial(n, res) {
        if (n === 0) {
            return res;
        }
        return inner_factorial(n - 1, res * n);
    }
    return inner_factorial(n, 1);
}

이 ν•¨μˆ˜κ°€ μ–΄λ–»κ²Œ μ‹€ν–‰λ˜λŠ”μ§€ μ‚΄νŽ΄λ³΄μž.

  • factorial(6)
    • λ‚΄λΆ€ 읡λͺ… ν•¨μˆ˜(iaf) λŠ”(n = 6, res = 1)
      • iaf(5, 1 * 6)
        • iaf(4, 6 * 5)
          • iaf(3, 30 * 4)
            • iaf(2, 120 * 3)
              • iaf(1, 360 * 2)
                • iaf(0, 720)
                  • 720
                • 720
              • 720
            • 720
          • 720
        • 720
      • 720
    • iaf (6, 1) = 720
  • factorial(6) = 720

μŠ€νƒμ„ ν’€κ³  λ‚˜μ„œ 계산을 μˆ˜ν–‰ ν•  ν•„μš”κ°€ μ—†λ‹€λŠ” 것을 μ•Œ 수 μžˆλ‹€. 방금 값을 λ°˜ν™˜ν–ˆλ‹€. κ·ΈλŸ¬λ‚˜ κ·œμΉ™μ— 따라 μ²΄μΈμ—μ„œ λ‚˜μ€‘μ— μ‚¬μš©ν•˜μ§€ μ•Šλ”λΌλ„ μƒνƒœλ₯Ό μŠ€νƒ ν”„λ ˆμž„μœΌλ‘œ μ €μž₯ν•΄μ•Όν–ˆλ‹€.

κ·ΈλŸ¬λ‚˜ κ·œμΉ™μ€ λͺ¨λ“  언어에 μ μš©λ˜λŠ” 것은 μ•„λ‹ˆλ‹€. μ‹€μ œλ‘œ, Schemeμ—μ„œλŠ” μ΄λŸ¬ν•œ 체인을 tail call μ΅œμ ν™”λ‘œ μ΅œμ ν™”ν•΄μ—¬ν•œλ‹€. 이λ₯Ό 톡해 μŠ€νƒμ— λΆˆν•„μš”ν•œ ν”„λ ˆμž„μ΄ μ±„μ›Œμ§€μ§€ μ•ŠλŠ”λ‹€. 우리의 μœ„ μ˜ˆμ œλŠ” λ‹€μŒκ³Ό 같이 보일 것이닀.

  • factorial(6)
  • iaf(6, 1)
  • iaf(5, 6)
  • iaf(4, 30)
  • iaf(3, 120)
  • iaf(2, 360)
  • iaf(1, 720)
  • iaf(0, 720)
  • 720

결과적으둜 λ”μ°ν•˜κ²Œ 보인닀.

즉, μž¬κ·€λ₯Ό μ‚¬μš©ν•˜λ”λΌλ„ 반볡적 인 ν”„λ‘œμ„ΈμŠ€κ°€ μžˆλ‹€.

쒋은 μ†Œμ‹μ€ ES6의 ν•¨μˆ˜μ΄λ‹€. μž¬κ·€ 호좜이 ν…ŒμΌ μœ„μΉ˜μ— 있고 ν•¨μˆ˜μ— strict λͺ¨λ“œκ°€ 있으면 ν…ŒμΌ 호좜 μ΅œμ ν™”κ°€ μ‹œμž‘λ˜κ³  maximum stack size exceeded였λ₯˜κ°€ λ°œμƒν•˜μ§€ μ•ŠλŠ”λ‹€.

μ—…λ°μ΄νŠΈ 2017 λ…„ 12 μ›” 1 일 : ν…ŒμΌ 콜 μ΅œμ ν™” κΈ°λŠ₯μ΄μžˆλŠ” μœ μΌν•œ μ£Όμš” λΈŒλΌμš°μ €λŠ” Safari이닀. V8에 영ν–₯을 μ£Όμ§€λ§Œ 2 κ°€ 아직 λ‚˜μ˜€μ§€ μ•Šμ€ μ΄μœ λŠ” μ•„λž˜ 3가지이닀.

1: https://kangax.github.io/compat-table/es6/#test-proper_tail_calls_(tail_call_optimisation)

2: https://bugs.chromium.org/p/v8/issues/detail?id=4698

3: https://v8project.blogspot.com/2016/04/es6-es7-and-beyond.html