1. 程式人生 > >Scala函數語言程式設計原理 第二課 程式設計的本質(Elements of programing)

Scala函數語言程式設計原理 第二課 程式設計的本質(Elements of programing)

        從這周開始,我將開始學習Scala程式設計,我們將由淺入深的學習這門新的語言和函數語言程式設計正規化。這堂課的大部分內容對你來講將會非常的簡單易懂,因為這都是你熟悉的東西。但是,也有一些東西是對以後學習打下根基的東西,特別像是我們稱之為“代替模型”的求值前的模型等知識,將會對以後的課程非常的重要,希望大家多多注意。好的,讓我們開始吧:

        每一門非凡的計算機語言都有以下幾點:

       1、表達最簡單資料單元的原始表示式

       2、提供各種結合表示式的方式

       3、提供抽象這些表示式的途徑,可以讓這些表示式抽象成名字然後被呼叫等等。

       運用函數語言程式設計有點像用計算器,一種很好的學習方式就是用語言本身提供的互動式的shell,你可以利用它書寫一些表示式,它會返回給你所寫表示式的值,Scala提供的是叫REPL的小工具。(Read-Eval-Print-Loop)想要啟動Scala的REPL工具只需在命令列下敲出“scala”就可以:

     

         然後,你就可以寫Scala了。如果出現以上的東西,表明你的Scala安裝正確了,但是在我們的課程中,我們不會講Scala基礎安裝的東西,這不屬於這堂課的範疇,我們只會 講SBT(Scala 包管理工具,類似於Java中的Maven)等東西的安裝。在SBT中,你照樣可以得到類似於這個REPL互動命令列,但是你輸入的東西將是“sbt console”而不是“Scala”。

    在Scala的REPL下,你可以輸入“34+65”;或者定義一個函式,都會返回相應的值:

  

 好的,下面讓我們來一起看到了資料的求值,資料的求值遵循的是代數的基本法則,對於一個非原始表示式的求值遵循以下原則:

  1、用最左邊的操作符,

  2、對運算元求值(從左往右)

  3、把操作符運用於運算元

  那麼,對於一個name的求值是怎樣的呢?對於一個name的求值,就是對於定義這個name右邊的內容的求值。 求值過程一旦遇到返回值,則求值過程就會終止。在此時刻,返回的值就是一個數字(如上圖)。之後,我們還會考慮其他型別的返回值。

  下面就是一個數學表示式的求值:

   (2*PI)*radius

  首先我們會求出PI的值

     (2*3.14159)*radius

   然後我們會求出括號中的值

    6.28318*radius

   然後求出radius的值

   6.28318*10

   最後得到結果,並返回結果

  62.8318

  定義表示式(definitions )還可以包含引數,我們可以利用“def square“ 語句來定義一個square函式,然後傳一個double型別的引數x,等號右邊的x都是引數傳來的x,於是咱們可以算得square(2)等於2乘以2等於4,以此類推,得到square(5+4)等於81.0,square(square(4))等於256.0。我們還可以定義square的和,如下所示。

我們可以發現,在最後一行,函式的引數後面跟了一個數值型別,這個數值型別在引數的冒號之後,在Scala中,函式的引數符合這種規定。你也可以給一個此函式的返回值,在Scala中是寫在引數的後面,如下所示:

def pow(x:Double, y: Int): Double = ...

Scala的原始資料型別都來自於Java,但是是大寫的:

Int                  32位整型數字

Double          64位浮點型數字

Boolean        布林型的值 (true或者是false)

那麼一個函式程式是如何求值的呢?

程式的引數化的函式的求值和運算元的求值方式類似:

1、從左到右依次求出所有函式引數的值

2、把函式程式由函式右邊的內容所代替,函式的結果都是等號右邊得到的結果

3、與此同時,把形參全部換成真實的引數

下面,我們通過一個例子來看一下:

sumOfSquares(3, 2+2)    

==>    sumOfSquare(3, 4)

==>    square(3) + square(4)

==>    3 * 3 + square(4)   

==>     9  +   square(4)    

==>     9 +  16                  

==>     25

這種格式的表示式求值被稱為"代替模型"或者“替換模型”。

這個想法成為一種模型的原因在於:表示式可以等價於一個值。(所有表示式都是值)

這可以被所有表示式所使用,只要他們沒有副作用。什麼是副作用?一個副作用肯定呼叫了是表示式,例如:c是一個可變變數,“c++”的意思就是每次執行“c++”c就會加一。“C++”這個表示式就是有副作用的,因為沒用一次,就會產生新的值,一個表示式無論什麼時候呼叫,都返回同一個值,這樣的表示式就可以被稱之為沒有副作用。

這種“代替模型”在lamda表示式中得到了很好的實現,拉姆達表示式是函數語言程式設計打下了根基。

總結:一旦我們有了“代替模型”,另外一個問題就出現了,是否每一個表示式都能轉化成單獨的一個值(在有限的很少的步驟之內)?

事實上,答案是:不行。下面有一個反例:

有一個簡單的函式名字叫loop,返回值是Int型,指向的卻是loop

def loop:Int = loop

這會出現什麼情況呢?根據我們的取值流程,我們必須求出函式的值:把函式右邊的求值過程代替左邊的函式名。但是,右邊的步驟卻又是loop。

loop ===> loop

所以,我們把表示式沒有簡化到一個值,而是又轉到了自己身上,並且這種轉化將會一直進行下去。。。

loop ===> ...

另外一種方式去更形象化的理解這種迴圈步驟,從自身名字開始相當於畫了一個圓圈,然後又從頭開始畫圈。。。


直譯器會把函式的引數在重新執行函式程式的時候減少成為一個值,這不是唯一的解決方案。換句話說,我們可以提供一個不減少引數的方式,例如:

sumOfSquares(3, 2+2)

==>   square(3) +  square(2 + 2)

==>   3*3   +   square(2+2)

==>   9   +  square(2+2)

==>   9   +  (2+2) * (2+2)

==>   9   +   4 * 4

==>  25

現在你發現了兩種方式去對相同的表示式求值,第一種方式叫做 all by value, 第二種方式叫做 call by name。

兩種方式都會被簡化成了一個相同的數值,只要滿足以下條件:

1、被簡化的表示式包含純函式,

2、兩種求值方式都會終止而不是無線迴圈。

call by value 有一個特點就是會一次性的求出所有引數的數值。

call by name 有一個特點是如果說函式的引數沒有被用到,它就不會求出它的值,引數只有在被用到的時候,才會去求值。

下面有一個問題:有如下函式:

def test (x: Int, y: Int) = x * x

對於一下表達式的使用,指出"call by name"和"call by value"哪種求值方式更快?(或者說有更少的求值步驟?)

test(2,3)

test(3+4, 8)

test(7, 2*4)

test(3+4, 2*4)

讓我們來一起看看這個問題,讓我們從第一個開始:

test(2,3)

call by value:==> 2*2 ==> 4

call by name:==> 2*2  ==>4

很顯然,這種方式call by name和call by value得到的步驟都是以上的方式,所以兩種求值方式所用的步驟是一樣的。

來看第二種:

test(3+4, 8)

call by value: ==> test(7, 8)      ==> 7*7        ==>49

call by name: ==>(3+4) * (3+4)==> 7*(3+4)  ==>7*7 ==> 49

很顯然,call by value方式取勝

來看第三種:

test(7, 2*4)

call by value : ==> test(7,8) ==> 7*7  ==>49

call by name: ==>  7*7        ==>49

call by name取勝。我們來看最後一個:

test(3+4, 2*4)

call by value:==> test(7, 2*4)   ==>  test(7,8) ==> 7*7 ==>49

call by name:==>(3+4) * (3+4) ==>  7*(3+4)   ==>7*7 ==> 49

又是一樣的

視訊地址:https://www.coursera.org/learn/progfun1/lecture/vzbJj/lecture-1-2-elements-of-programming