程式設計科學/封裝操作
回想一下,我們評估了一系列表示式以找到直線上一點的y值
給定一個x值
sway> var m = 6;
INTEGER: 6
sway> var x = 9;
INTEGER: 9
sway> var b = -12;
INTEGER: -12
sway> var y = m * x + b;
INTEGER: 42
sway> y;
INTEGER: 42
現在,假設我們希望找到與不同x值對應的y值,或者更糟糕的是,對於不同直線上的不同x值。我們所做的一切工作都必須重複進行。函式是一種封裝所有這些操作的方法,這樣我們就可以用最少的努力重複它們。
首先,我們將定義一個不太有用的函式,它計算給定斜率為6、y截距為-12和x值為9時的y值(與上面完全相同)。
sway> function findY()
more> {
more> var slope = 6;
more> var x = 9;
more> var intercept = -12;
more> return slope * x + intercept;
more> }
FUNCTION: <function findY()>
請注意,直譯器提示會更改為more 當輸入不完整時。在上面的函式定義的情況下,當輸入花括號結束符時會發生這種情況(1)。
一旦函式被定義,我們就可以重複找到y的值
sway> findY()
INTEGER: 42
sway> findY()
INTEGER: 42
這個函式不太有用,因為我們不能用它來計算類似的東西,比如不同x值的y值。一個好的函式的標誌是它可以讓你計算不止一件事。我們可以修改我們的函式來接收我們感興趣的x值。我們透過傳遞一個引數來實現這一點,在本例中,x的值。
sway> function findY(x)
more> {
more> var slope = 6;
more> var intercept = -12;
more> return slope * x + intercept;
more> }
FUNCTION: <function findY()>
我們透過在函式定義括號之間放置變數名來給傳遞的值命名。在本例中,我們選擇了x作為名稱。請注意,由於我們正在傳遞x,因此我們不再需要(或不需要)x的定義,因此我們將其刪除。現在我們可以計算無限多個x的y值。
sway> findY(9);
INTEGER: 42
sway> findY(0);
INTEGER: -12
sway> findY(-2);
INTEGER: -24
如果我們希望針對不同直線上的給定x值計算y值怎麼辦?一種方法是傳遞斜率和截距以及x
sway> function findY(x,slope,intercept)
more> {
more> return slope * x + intercept;
more> }
FUNCTION: <function findY()>
sway> findY(9,6,-12);
INTEGER: 42
sway> findY(0,6,-12);
INTEGER: -12
如果我們希望使用不同的直線進行計算,我們只需將新的斜率和截距以及x的值傳遞進來。這當然可以按預期工作,但這不是最好的方法。一個問題是我們必須不斷輸入斜率和截距,即使我們正在計算同一直線上的y值。每當你發現自己一遍又一遍地做同樣的事情時,請放心,有人已經想出了避免這種特定乏味的辦法。所以,假設情況確實如此,我們如何自定義我們的函式,以便我們只需為每條特定直線輸入一次斜率和截距?我們將探討三種不同的方法。在進一步閱讀時,瞭解所有正在發生的事情並不重要。重要的是你知道其他方法存在,並瞭解每種方法的優缺點
在這一點上,你應該看到你可以建立函式,並且應該能夠透過模式匹配來建立其他函式。例如,你應該能夠定義一個函式來對給定數字進行平方
function square(x)
{
return x * x;
}
由於建立函式是一項艱苦的工作(很多打字),而且計算機科學家像躲避瘟疫一樣躲避艱苦的工作,所以很早以前就有人想到了編寫一個本身建立函式的函式!太棒了!我們可以為我們的直線問題做到這一點。我們將告訴我們的創意函式為特定的斜率和截距建立一個findY函式!
function makeFinder(slope,intercept)
{
function findY(x)
{
return slope * x + intercept;
}
return findY;
}
看看我們的創意函式是如何定義一個findY函式,然後將其返回的。現在我們可以建立一大堆不同的findY函式。
sway> var findYa = makeFinder(6,-12);
FUNCTION: <function findY(x)>
sway> var findYb = makeFinder(5,2);
FUNCTION: <function findY(x)>
sway> findYa(9);
INTEGER: 42
sway> findYb(9);
INTEGER: 47
請注意findYa和findYb是如何記住它們在建立時提供的斜率和截距的。雖然這無疑很酷,但問題是許多語言(包括C和Java)不允許你定義建立其他函式的函式。
解決我們的直線問題的另一種方法是使用一個叫做物件的東西。在Sway中,物件只是一個環境,我們之前見過這些。所以這裡沒有什麼新東西,只是在如何使用物件來實現我們的目標方面有所不同。在這裡,我們定義一個建立函式(稱為建構函式),它建立並返回一個直線物件。
function line(slope,intercept)
{
return this;
}
this變數始終指向當前環境,在本例中,包括斜率和截距。透過返回this,我們返回此環境,並且我們可以隨時查詢斜率和截距的值。為了證明斜率和截距的存在,我們可以使用內建的bindings函式
sway> lineA = line(6,-12);
sway> bindings(lineA);
OBJECT 231:
context : <object 145>
dynamicContext: <object 145>
constructor: <function line(slope,intercept)>
this: <object 231>
intercept: -12
slope : 6
SYMBOL: :true
我們使用 '.'(點)運算子訪問物件中的變數
sway> lineA . slope;
INTEGER: 6
sway> lineA . constructor;
FUNCTION: <function line(slope,intercept)>
現在我們修改我們的findY函式,使其接收一個直線物件以及x,並使用點運算子提取直線的斜率和截距
function findY(line,x)
{
return line . slope * x + line . intercept;
}
在這種情況下,我們建立不同的直線,然後將每條直線傳遞給我們的新findY函式
sway> var lineA = line(6,-12);
OBJECT: <object 231>
sway> var lineB = line(5,2);
OBJECT: <object 256>
sway> findY(lineA,9);
INTEGER: 42
sway> findY(lineB,9);
INTEGER: 47
這種方法的問題是我們將直線物件與查詢y值分開了,但這兩個概念密切相關。例如,假設我們有拋物線物件和直線物件。我們的findY函式將無法為拋物線物件正常工作,即使拋物線上(x,y)點的概念與直線上的點一樣有效(2)。在面向物件的世界上,我們透過將物件和專門對該物件起作用的函式捆綁在一起解決了這個問題。在我們的例子中,我們將findY函式作為直線物件的一部分
function line(slope,intercept)
{
function findY(x)
{
return slope * x + intercept;
}
this;
}
這與動態函式方法非常相似,但我們返回this而不是findY。現在我們透過直線物件呼叫findY函式。
sway> var lineA = line(6,-12);
OBJECT: <object 231>
sway> var lineB = line(5,2);
OBJECT: <object 256>
sway> lineA . findY(9);
INTEGER: 42
sway> lineB . findY(9);
INTEGER: 47
如果我們有一個拋物線物件,它將有自己的findY函式,但實現方式不同。但是,我們以同樣的方式呼叫它
sway> var parabolaA = parabola(2,0,0);
OBJECT: <object 453>
sway> parabolaA . findY(7);
INTEGER: 49
這種方法在面向物件的語言(如Java)中得到支援。早期的方法(函式與物件分離)在過程式語言(如C)中得到支援。
(1) 在直譯器中鍵入一個長結構很乏味。稍後我們將學習如何將程式碼儲存在檔案中,並讓直譯器執行該檔案中的程式碼。如果我們需要更改程式碼,我們只需編輯該檔案即可。這樣,我們就不需要從頭開始將修改後的程式碼鍵入直譯器。
(2) 這是一個試圖泛化的具體例子,以便我們的函式適用於所有函式概念有效的物件。