前面使用let和proc,可以進(jìn)行函數(shù)的定義,但是并不支持在函數(shù)內(nèi)部遞歸地調(diào)用改函數(shù),究其原因,是在函數(shù)調(diào)用時(shí),即proc的body語句執(zhí)行的時(shí)候,它所引用的環(huán)境env,是在let語法中,擴(kuò)展指向procedure的變量之前,所以在當(dāng)時(shí)的env中,是沒有指向該函數(shù)的變量的。 本文使用letrec擴(kuò)展語法,實(shí)現(xiàn)函數(shù)的遞歸調(diào)用。
代碼在https://github.com/wangdxh/eopl3-in-python ?letrec目錄下
letrec語法如下:
letrec 后跟著 函數(shù)的名稱,一對(duì)圓括號(hào)內(nèi)包含著函數(shù)的參數(shù)名稱,緊接著是 = 和 函數(shù)體,最后 in 和 要執(zhí)行的body語句。
letrec解析的時(shí)候,函數(shù)的名稱,參數(shù),函數(shù)體,會(huì)直接解析出來,這里將函數(shù)的定義擴(kuò)展進(jìn)環(huán)境env,let+proc 擴(kuò)展的時(shí)候通過procedure,將變量,body,“當(dāng)時(shí)的環(huán)境”,通過閉包生成一個(gè)函數(shù),當(dāng)這個(gè)函數(shù)體真正執(zhí)行的時(shí)候,所使用的env,生成函數(shù)時(shí)“當(dāng)時(shí)的環(huán)境”+變量和其賦值的擴(kuò)展。所以let聲明的指向函數(shù)的變量,并不在執(zhí)行的env中。
這里擴(kuò)展進(jìn)環(huán)境變量的時(shí)候,不用procedure生成一個(gè)閉包的函數(shù),而是把相應(yīng)的函數(shù)名稱,參數(shù),和body擴(kuò)展到環(huán)境中。
現(xiàn)在有2個(gè)向環(huán)境中擴(kuò)展變量和其對(duì)應(yīng)值的函數(shù)。
從環(huán)境中查找變量的時(shí)候,進(jìn)行了調(diào)整,當(dāng)發(fā)現(xiàn)查找的是letrec對(duì)應(yīng)的函數(shù)名稱時(shí),將其函數(shù)參數(shù),body,和正在查找的env(注意這個(gè)env很重要,這是最小包含著函數(shù)名稱的環(huán)境,相當(dāng)于是letrec,將函數(shù)名稱擴(kuò)展到環(huán)境中后的那個(gè)環(huán)境)放在一個(gè)list里面,原來的其他變量和單個(gè)對(duì)應(yīng)值查找也進(jìn)行了微調(diào),也放在list里,便于統(tǒng)一處理。
代碼中唯一用到查找變量的地方就是var_fun,這里針對(duì)變量的名稱,去查找對(duì)應(yīng)的值,如果發(fā)現(xiàn)有3個(gè)元素的list,就是letrec定義的函數(shù),1個(gè)元素就是原來的處理。如果是letrec的信息,就使用proceduer函數(shù)生成一個(gè)閉包函數(shù),去執(zhí)行,和let一致,這里注意list內(nèi)的最后一個(gè)env,是最小的包含著letrec定義的函數(shù)名的環(huán)境。所以這個(gè)時(shí)候在函數(shù)調(diào)用的時(shí)候,在其環(huán)境中是包含著letrec定義的函數(shù)名的,借此來完成遞歸調(diào)用。
整個(gè)完成遞歸流程的處理點(diǎn)就是,生成procedure的時(shí)候,最后的env環(huán)境是包含著letrec定義的函數(shù)名的最小的環(huán)境集合。
下圖是一個(gè)測(cè)試一個(gè)數(shù)值是否為奇數(shù)的例子