天天看點

JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)

這是Jerry 2021年的第 12 篇文章,也是汪子熙公衆号總共第 283 篇原創文章。

今天是2021年1月20日,看看曆史上的今天都發生了什麼。

2004年1月20日,第一個公開版本的Scala釋出。

JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)

Scala是一種采用靜态類型系統的編譯型語言,具有很強的可擴充性(Scalability),這也是其名稱的由來。

Scala設計初衷是內建面向對象程式設計和函數式程式設計的各種特性,運作于JVM平台上,并相容已有的Java程式。

JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)

Jerry沒有在SAP标準産品開發中使用過Scala,隻是完成2015年公司一個内部教育訓練布置的課程作業中,使用Scala在Spark上開發了一個最簡單的demo:統計海量英文圖書裡,計算出使用頻率最高的十大單詞。

JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)
JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)
JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)
JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)

尾遞歸函數存在的意義是什麼呢?要回答這個問題,我們可以先在單步調試模式下,觀察正常遞歸函數的執行過程。

我們首先使用正常遞歸函數,計算5的階乘。

輸入參數n為5,執行到第7行,5的階乘等于5乘以4的階乘。單步調試進去,輸入參數n = 5, 進入第7行,準備執行 5 * factorial(4) .

JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)

注意觀察下圖的Call Stack清單,此時我們已經有兩個factorial函數的調用棧幀了。

什麼是棧幀?複習一下大學計算機原理學到的知識:在函數執行過程中,每一個函數調用都會把目前函數的調用資訊和内部變量儲存在棧裡面,稱為一個棧幀(Stack Frame).

其中下圖序号為1的棧幀,儲存了n = 5的計算上下文;序号為2的棧幀即目前最頂層的棧幀,儲存了n = 4的計算上下文。

JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)
JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)
JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)
JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)

下面再來看看用尾遞歸實作的階乘。

下圖第20行語句是以尾遞歸方式計算5的階乘入口,調用尾遞歸函數tailFactorial,注意函數的第二個輸入參數total,這個參數用于存儲目前階乘的計算結果。

JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)

這個尾遞歸函數的結束條件是,當第一個輸入參數n為1時,就把第二個輸入參數的值,作為階乘運算的最終結果傳回。第二個參數實際上存放的,是目前遞歸調用的階乘計算結果。

當n大于1時,遞歸尚未滿足退出條件,此時首先将n和目前的階乘計算結果(變量total)相乘,将乘積作為第二個輸入參數,傳遞到下一層遞歸調用的棧幀中去。

下圖是tailFactorial函數内部,即将進入第一輪遞歸調用的棧幀:

JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)

第一輪遞歸調用的棧幀内部,序号為2.

注意,此時序号為1的棧幀已經完全不再需要了,因為我們繼續進行遞歸調用的所需資訊,都已經包含在第16行tailFactorial調用的兩個輸入參數裡了,此時n為上一層遞歸調用傳入的5 - 1 = 4,total為上一輪傳入的5 × 1 = 5. 進行下一輪遞歸調用,兩個輸入參數的值分别是4 - 1 = 3和4 * 5 = 20.

JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)

進入第三層遞歸調用,此時輸入參數 n = 3,total = 20,均為上一層調用傳入。

注意,下圖示号為1和2的兩個棧幀,實際上不再需要了,因為要繼續進行遞歸調用的所有輸入資訊,都已經存儲在标号為3的棧幀裡了

JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)
JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)

因為按照尾遞歸版本的階乘實作,每一輪階乘的遞歸計算結果,已經通過第二個參數total儲存了下來,是以沒有必要再用一個完整的棧幀,去儲存目前這輪遞歸計算的函數調用上下文了。這就引出了所謂“尾遞歸優化”的概念:

When a compiler detects a call that is tail recursive, it overwrites the current activation record instead of pushing a new one onto the stack. The compiler can do this because the recursive call is the last statement to be executed in the current activation; thus, there is nothing left to do in the activation when the call returns. Consequently, there is no reason to keep the current activation around. By replacing the current activation record instead of stacking another one on top of it, stack usage is greatly reduced, which leads to better performance in practice. Thus, we should make recursive functions tail recursive whenever we can.

https://www.oreilly.com/library/view/mastering-algorithms-with/1565924533/ch03s02.html

上述文字大意如下:

當(C語言)編譯器檢測到尾遞歸調用時,并不會建立新的棧幀并壓入棧中,而是用新的棧幀覆寫掉目前處于激活狀态的棧幀。編譯器之是以能夠這樣做,是因為尾遞歸函數裡,遞歸調用是目前棧幀裡最後一個需要執行的函數調用。被覆寫掉的棧幀本身毫無用處,不需要再保留。采用棧幀覆寫,而不是建立棧幀的方式,極大程度上減少了棧幀的個數,提高了遞歸函數的執行性能。是以,應該盡可能地去嘗試使用尾遞歸方式實作遞歸函數。

一個實際的性能比較例子:計算20的階乘,二者的性能有巨大差異:普通遞歸實作需要10毫秒,而尾遞歸實作僅僅需要不到1毫秒的時間。

JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)
JavaScript, ABAP和Scala裡的尾遞歸(Tail Recursion)

至于ABAP編譯器能否支援尾遞歸優化?我沒有研究過,我隻是覺得,尾遞歸優化并不能算是ABAP編譯器必須實作的需求之一。

希望本文能幫助大家對尾遞歸優化這個概念有一個最基本的認識,感謝閱讀。

ABAP專題

Jerry的ABAP, Java和JavaScript亂炖

ABAP開發人員未來應該學些什麼

Jerry 2017年的五一小長假:8種經典排序算法的ABAP實作

Jerry的ABAP原創技術文章合集

300行ABAP代碼實作一個最簡單的區塊鍊原型

使用Java+SAP雲平台+SAP Cloud Connector調用ABAP On-Premise系統裡的函數

在SAP雲平台的CloudFoundry環境下消費ABAP On-Premise OData服務

ABAP vs Java, 蛙泳 vs 自由泳

聊聊C語言和ABAP

動手使用ABAP Channel開發一些小工具,提升日常工作效率

我用ABAP做過的那些無聊的事情

不喜歡SAP GUI?那試試用Eclipse進行ABAP開發吧

使用Visual Studio Code編寫和激活ABAP代碼

你的ABAP程式給佛祖開過光麼?來試試Jerry這個小技巧

在SAP雲平台ABAP程式設計環境上編寫第一段ABAP程式

SAP官方釋出的ABAP程式設計規範

ABAP Code Inspector那些隐藏的功能,您都知道嗎?

還在用ABAP進行SAP産品的二次開發?來了解下這種全新的二次開發理念吧

ABAP Netweaver體内的那些寄生式程式設計語言

從SAP社群上的一篇部落格開始,聊聊SAP産品命名背後的那份情懷

雲端的ABAP Restful服務開發

如何在SAP雲平台ABAP程式設計環境裡把CDS view暴露成OData服務

使用abapGit在ABAP On-Premises系統和SAP雲平台ABAP環境之間進行代碼傳輸

30分鐘用Restful ABAP Programming模型開發一個支援增删改查的Fiori應用

Jerry帶您了解Restful ABAP Programming模型系列之二:Action和Validation的實作

Jerry帶您了解Restful ABAP Programming模型系列之三:雲端ABAP應用調試

SAP雲平台上的ABAP程式設計環境裡如何消費第三方服務

ABAP開發者上雲的時候到了 - 現在大家可以免費使用SAP雲平台ABAP環境的試用版了

學而不思則罔 - SAP雲平台ABAP程式設計環境的由來和适用場景

SAP雲平台裡的三叉戟應用

如何基于Restful ABAP Programming模型開發并部署一個支援增删改查的Fiori應用

SAP 2019 TechEd Key Note解讀:雲時代下SAP從業人員如何做二次開發?

有哪些ABAP關鍵字和文法,到了ABAP雲環境上就沒辦法用了?

ABAP開發環境終于支援以駝峰命名法自動格式化ABAP變量名了

利用ABAP 740的新關鍵字REDUCE完成一個實際工作任務

一段讓人瑟瑟發抖的ABAP代碼

昨日萬聖節ABAP怪獸級代碼謎團,公布答案啦

介紹一種在ABAP核心态進行内表高效拷貝的方法

使用SAP Cloud Application Programming模型開發OData的一個實際例子

當ABAP遇見普羅米修斯

使用ABAP繪制可伸縮矢量圖

ABAP開發環境文法高亮的那些事兒

SAP錯誤消息調試之七種武器:讓所有的錯誤消息都能被定位

使用ABAP操作Excel的幾種方法

SAP GUI裡的收藏夾事務碼管理工具

SAP GUI和Windows系統資料庫

有了Debug權限就能幹壞事?小心了,你的一舉一動盡在系統監控中

ABAP CCDEF, CCIMP, CCMAC, CCAU, CMXXX這些東東是什麼鬼

實作ABAP條件斷點的三種方式

使用SAT跟蹤監控從浏覽器打開的SAP應用的性能和調用棧

一個13年ABAP老兵的建議:了解這些基礎知識,對ABAP開發有百利而無一害

SAP ABAP Netweaver容器化, 不可能完成的任務嗎?

SAP産品增強技術回顧

SAP API開發方法大全

淺談Java和SAP ABAP的靜态代理和動态代理,以及ABAP面向切面程式設計的嘗試

SAP ABAP應用伺服器的HTTP響應狀态碼(Status Code)

SAP ABAP裡存在Java List這種集合工具類麼?CL_OBJECT_COLLECTION了解一下

ABAP面試題系列:寫一組會出現死鎖(Deadlock)的ABAP程式

SAP ABAP Netweaver伺服器的标準登入方式講解

SAP ABAP關鍵字文法圖和ABAP代碼自動生成工具Code Composer

SAP ABAP SM50的另類用途 - ABAP工作程序對資料庫表讀取操作的檢測

關于SAP ABAP字元變量和字元串變量字元個數的一個知識點,和一個血案

SAP ABAP一組關鍵字 IS BOUND, IS NOT INITIAL和IS ASSIGNED的用法辨析

SAP ABAP和Java裡的弱引用(WeakReference)和軟引用(SoftReference)

SAP AMDP介紹 - ABAP托管的HANA資料庫過程

給你的ABAP對象打上标簽(Tag)

曆史上的今天:程式設計語言中null引用的十億美元錯誤