第四章 定義函數(shù)

2018-02-24 15:45 更新

4.1?簡(jiǎn)介

在前面的章節(jié)中,我已經(jīng)講解了:

  1. 如何安裝MIT-Scheme;
  2. Scheme解釋器是如何對(duì)S-表達(dá)式求值;
  3. 基本的表操作;

在本章中,我會(huì)講解如何自定義函數(shù)。由于Sheme是函數(shù)式編程語言,你需要通過編寫小型函數(shù)來構(gòu)造程序。因此,明白如何構(gòu)造并組合這些函數(shù)對(duì)掌握Scheme尤為關(guān)鍵。在前端定義函數(shù)非常不便,因此我們通常需要在文本編輯器中編輯好代碼,并在解釋器中加載它們。

4.2?如何定義簡(jiǎn)單函數(shù)并加載它們

你可以使用define來將一個(gè)符號(hào)與一個(gè)值綁定。你可以通過這個(gè)操作符定義例如數(shù)、字符、表、函數(shù)等任何類型的全局參數(shù)。

讓我們使用任意一款編輯器(記事本亦可)來編輯代碼片段1中展示的代碼,并將它們存儲(chǔ)為hello.scm,放置在類似于C:\doc\scheme\的文件夾下。如果可以的話,把這些文件放在你在第一章定義的MIT-Scheme默認(rèn)文件夾下。

代碼片段1(hello.scm)

; Hello world as a variable
(define vhello "Hello world")     ;1

; Hello world as a function
(define fhello (lambda ()         ;2
         "Hello world"))

接下來,向Scheme解釋器輸入下面的命令:

(cd "C:\\doc\\scheme")
;Value 14: #[pathname 14 "c:\\doc\\scheme\\"]

(load "hello.scm")
;Loading "hello.scm" -- done
;Value: fhello

通過這些命令,hello.scm就被加載到解釋器中。如果你的當(dāng)前目錄被設(shè)定在了腳本所在目錄,那么你就不需要再輸入第一行的命令了。然后,向解釋器輸入下面的命令:

vhello
;Value 15: "Hello world"

fhello
;Value 16: #[compound-procedure 16 fhello]

(fhello)
;Value 17: "Hello world"

操作符define用于聲明變量,它接受兩個(gè)參數(shù)。define運(yùn)算符會(huì)使用第一個(gè)參數(shù)作為全局參數(shù),并將其與第二個(gè)參數(shù)綁定起來。因此,代碼片段1的第1行中,我們聲明了一個(gè)全局參數(shù)vhello,并將其與"Hello,World"綁定起來。

緊接著,在第2行聲明了一個(gè)返回“Hello World”的過程。

特殊形式lambda用于定義過程。lambda需要至少一個(gè)的參數(shù),第一個(gè)參數(shù)是由定義的過程所需的參數(shù)組成的表。因?yàn)楸纠?code>fhello沒有參數(shù),所以參數(shù)表是空表。

在解釋器中輸入vhello,解釋器返回“Hello,World”。如果你在解釋器中輸入fhello,它也會(huì)返回像下面這樣的值:#[compound-procedure 16 fhello],這說明了Scheme解釋器把過程和常規(guī)數(shù)據(jù)類型用同樣的方式對(duì)待。正如我們?cè)谇懊嬲鹿?jié)中講解的那樣,Scheme解釋器通過內(nèi)存空間中的數(shù)據(jù)地址操作所有的數(shù)據(jù),因此,所有存在于內(nèi)存空間中的對(duì)象都以同樣的方式處理。

如果把fhello當(dāng)過程對(duì)待,你應(yīng)該用括號(hào)括住這些符號(hào),比如(fhello)。

然后解釋器會(huì)按照第二章講述的規(guī)則那樣對(duì)它求值,并返回“Hello World”。

4.3?定義有參數(shù)的函數(shù)

可以通過在lambda后放一個(gè)參數(shù)表來定義有參數(shù)的函數(shù)。將代碼片段2保存為farg.scm并放在同hello.scm一致的目錄。

代碼片段2 (farg.scm)

; hello with name
(define hello
  (lambda (name)
    (string-append "Hello " name "!")))

; sum of three numbers
(define sum3
  (lambda (a b c)
    (+ a b c)))

保存文件,并在解釋器中載入此文件,然后調(diào)用我們定義的函數(shù)。

(load "farg.scm")
;Loading "farg.scm" -- done
;Value: sum3

(hello "Lucy")
;Value 20: "Hello Lucy!"

(sum3 10 20 30)
;Value: 60
Hello

函數(shù)hello有一個(gè)參數(shù)(name),并會(huì)把“Hello”、name的值、和"!"連結(jié)在一起并返回。

預(yù)定義函數(shù)string-append可以接受任意多個(gè)數(shù)的參數(shù),并返回將這些參數(shù)連結(jié)在一起后的字符串。

sum3:此函數(shù)有三個(gè)參數(shù)并返回這三個(gè)參數(shù)的和。

4.4?一種函數(shù)定義的短形式

lambda定義函數(shù)是一種規(guī)范的方法,但你也可以使用類似于代碼片段3中展示的短形式。

代碼片段3

; hello with name
(define (hello name)
  (string-append "Hello " name "!"))

; sum of three numbers
(define (sum3 a b c)
  (+ a b c))

在這種形式中,函數(shù)按照它們被調(diào)用的形式被定義。代碼片段2和代碼片段3都是相同的。有些人不喜歡這種短形式的函數(shù)定義,但是我在教程中使用這種形式,因?yàn)樗梢允勾a更短小。

練習(xí)1

按照下面的要求編寫函數(shù)。這些都非常簡(jiǎn)單但實(shí)用。

  1. 將參數(shù)加1的函數(shù)。
  2. 將參數(shù)減1的函數(shù)。

練習(xí)2

讓我們按照下面的步驟編寫一個(gè)用于計(jì)算飛行距離的函數(shù)。

  1. 編寫一個(gè)將角的單位由度轉(zhuǎn)換為弧度的函數(shù)。180度即π弧度。π可以通過下面的式子定義:?(define pi (* 4 (atan 1.0)))。
  2. 編寫一個(gè)用于計(jì)算按照一個(gè)常量速度(水平分速度)運(yùn)動(dòng)的物體,t秒內(nèi)的位移的函數(shù)。
  3. 編寫一個(gè)用于計(jì)算物體落地前的飛行時(shí)間的函數(shù),參數(shù)是垂直分速度。忽略空氣阻力并取重力加速度g9.8m/s^2。提示:設(shè)落地時(shí)瞬時(shí)豎直分速度為-Vy,有如下關(guān)系。2 * Vy?= g * t??> 此處t為落地時(shí)的時(shí)間。
  4. 使用問題1-3中定義的函數(shù)編寫一個(gè)用于計(jì)算一個(gè)以初速度v和角度theta擲出的小球的飛行距離。
  5. 計(jì)算一個(gè)初速度為40m/s、與水平方向呈30°的小球飛行距離。這個(gè)差不多就是一個(gè)臂力強(qiáng)勁的職業(yè)棒球手的投擲距離。

提示:首先,將角度的單位轉(zhuǎn)換為弧度(假定轉(zhuǎn)換后的角度為theta1)。初始水平、豎直分速度分別表示為:v*cos(theta1)v*sin(theta1)。落地時(shí)間可以通過問題3中定義的函數(shù)計(jì)算。由于水平分速度不會(huì)改變, 因此可以利用問題2中的函數(shù)計(jì)算距離。

4.5?關(guān)于編輯器

這里,我會(huì)推薦一些能非常方便地編輯Scheme代碼的編輯器。

4.5.1?Emacs

Emacs21的Windows版本可以從http://ftp.gnu.org/gnu/emacs/windows/找到,下載emacs-21.3-bin-i386.tar.gz并解壓它。

你會(huì)在bin文件夾下發(fā)現(xiàn)一個(gè)叫runemacs.exe的可執(zhí)行文件。雙擊這個(gè)程序來啟動(dòng)編輯器。盡管鍵位布局和Windows的標(biāo)準(zhǔn)相當(dāng)不同,但是因?yàn)橛幸粋€(gè)菜單欄和鼠標(biāo)控制器而顯得相當(dāng)用戶友好。你也可以通過編輯名為.emacs的配置文件來實(shí)現(xiàn)自定義配置。編輯器提供了一種Scheme模式,此模式下能夠編輯器能識(shí)別預(yù)定義單詞,以及通過Ctri-i或TAB鍵來自動(dòng)縮進(jìn)。除此之外,當(dāng)一個(gè)輸入一個(gè)右括號(hào)后,編輯器會(huì)自動(dòng)顯示與之匹配的左括號(hào)。

在Windows系統(tǒng)中,emacs不能夠與MIT-Scheme進(jìn)行交互。你只能手動(dòng)地儲(chǔ)存并加載源代碼。但從另一個(gè)方面來說,在UNIX和Linux系統(tǒng)下,emacs可以同MIT-Scheme進(jìn)行交互式地調(diào)用,因此編輯代碼也可以在交互中完成。

4.5.2?Edwin

Edwin是MIT-Scheme配備的編輯器。它有點(diǎn)像emacs18。但它沒有菜單欄和鼠標(biāo)控制,因此顯得不太用戶友好。只有少數(shù)人用這個(gè)編輯器,因此網(wǎng)絡(luò)上可用的說明也很少。雖然如此,你可以使用這個(gè)編輯器進(jìn)行交互式的代碼編輯。我在Windows上使用這個(gè)編輯器編輯Scheme代碼。

如何使用Edwin

雙擊Edwin的圖標(biāo)以啟動(dòng)Edwin。當(dāng)Edwin啟動(dòng)后,一個(gè)叫*Scheme*的默認(rèn)緩沖區(qū)出現(xiàn)在屏幕上,它對(duì)應(yīng)于emacs中的*scratch*緩沖區(qū)。你可以將*scheme*用作解釋器前端。按下Ctrl-X?Ctrl-e?就可以對(duì)S-表達(dá)式進(jìn)行求值。

  • 文件的打開與關(guān)閉,編輯器的關(guān)閉。按下Ctrl-X?Ctrl-F來打開一個(gè)文件。如果你指定的文件并不存在,則會(huì)創(chuàng)建一個(gè)新文件。初始路徑被設(shè)置為了‘C:\’,你在打開文件前應(yīng)該修改這個(gè)路徑。按下Ctrl-X?Ctrl-S來保存文件,而按下Ctrl-x?Ctrl-w則是文件另存為。退出編輯器請(qǐng)按下Ctrl-x?Ctrl-c。

  • 縮進(jìn)。按下Ctrl-i或者TAB可以縮進(jìn)。

  • 剪切,復(fù)制和粘貼。我們無法使用鼠標(biāo),因此復(fù)制(剪切)、粘貼起來就會(huì)顯得不太方便。但你可以像下面這樣做:

    • 首先,通過方向鍵將光標(biāo)移動(dòng)至待選區(qū)域的開頭,然后按下Ctrl-SPACE。
    • 然后移動(dòng)至結(jié)束位置按下Ctrl-w來剪切區(qū)域,按下Alt-w來復(fù)制區(qū)域。
    • 最后,移動(dòng)至你想要復(fù)制的區(qū)域,按下Ctrl-y。
  • 求值S-表達(dá)式

    • 按鍵Alt-z用于求值以define開頭的S-表達(dá)式。
    • 按鍵Alt-:用于在一個(gè)小型的緩沖區(qū)中求值S-表達(dá)式。這個(gè)通常用在測(cè)試用Alt-z求值的函數(shù)。
    • 按鍵Ctrl-x?Ctrl-e用于求值整個(gè)*scheme*緩沖區(qū)中的S-表達(dá)式。

請(qǐng)查閱Scheme用戶手冊(cè)以獲得更多關(guān)于Edwin的幫助。你下載的MIT-Scheme中也附帶了同樣的文檔。

4.6?小結(jié)

本章中,我講解了如何定義函數(shù)。特殊形式define用于定義函數(shù)和全局參數(shù)。我也講解了用合適的編輯器(比如emacs)來編輯源代碼,載入源碼文件比在前端直接定義函數(shù)方便多了。

下個(gè)章節(jié)中,我講介紹分支。

4.7?習(xí)題解答

4.7.1?答案1

; 1
(define (1+ x)
  (+ x 1))

; 2
(define (1- x)
  (- x 1))

4.7.2?答案2

代碼如下所示:

; definition of pi
(define pi (* 4 (atan 1.0)))

; degree -> radian
(define (radian deg)
  (* deg (/ pi 180.0)))

; free fall time
(define (ff-time vy)
  (/ (* 2.0 vy) 9.8))

; horizontal distance 
(define (dx vx t)
  (* vx t))

; distance
(define (distance v ang)
  (dx
   (* v (cos (radian ang)))                     ; vx
   (ff-time (* v (sin (radian ang))))))         ; t

向解釋器中載入后,距離可以像這樣計(jì)算:

(distance 40 30)
;Value: 141.39190265868385

函數(shù)返回一個(gè)合理的值:141.1米,因?yàn)楹雎粤丝諝庾枇?,所以這個(gè)值略微偏大。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)