1. 程式人生 > >函數語言程式設計簡介

函數語言程式設計簡介

一、程式設計正規化

"程式設計正規化"programming paradigm),也就是如何編寫程式的方法論

1.命令式程式設計:面向計算機硬體的抽象,變數(對應著儲存單元)、賦值語句(獲取,儲存指令),表示式(記憶體引用和算術運算)和控制語句(跳轉指令),命令式程式就是一個馮諾依曼機指令序列。面向物件程式設計就是一種指令式程式設計。

2.函式式程式設計:面向數學的抽象,將計算描述為一種表示式的值。函式式程式就是一個表示式。

函數語言程式設計與指令式程式設計的最大不同:describe what to do, rather than how to do it函式式程式設計關心資料的對映,指令式程式設計關心解決問題的

步驟

例子:計算(1 + 2) * 3 – 4

命令式:

var a = 1 + 2;

var b = a * 3;

var c = b - 4;

函式式:

add(1,2).multiply(3).subtract(4)

另一個例子:二叉樹映象反轉

                   

它的含義是:首先判斷節點是否為空;然後翻轉左樹;然後翻轉右樹;最後左右互換。所謂“翻轉二叉樹”,可以看做是要得到一顆和原來二叉樹對稱的新二叉樹。這顆新二叉樹的特點是每一個節點都遞迴地和原樹相反。這段程式碼體現的思維,就是舊樹到新樹的對映——對一顆二叉樹而言,它的映象樹就是左右節點遞迴映象的樹。

二、特點

1.函式"第一等公民"。所謂"第一等公民"first class),指的是函式與其他資料型別一樣,處於平等地位,可以賦值給其他變數,也可以作為引數,傳入另一個函式,或者作為別的函式的返回值

2.只用"表示式",不用"語句“。"表示式"expression)是一個單純的運算過程,總是有返回值;"語句"statement)是執行某種操作,沒有返回值。函數語言程式設計要求,只使用表示式,不使用語句。也就是說,每一步都是單純的運算,而且都有返回值。

3.沒有"副作用"(No Side Effect)。所謂"副作用"side effect),指的是函式內部與外部互動(最典型的情況,就是修改全域性變數的值),產生運算以外的其他結果。函數語言程式設計強調沒有

"副作用",意味著函式要保持獨立,所有功能就是返回一個新的值,沒有其他行為,尤其是不得修改外部變數的值。

4.不修改狀態。函數語言程式設計只是返回新的值,不修改系統變數。因此,不修改變數,也是它的一個重要特點

5.引用透明(Referential transparency)。指的是函式的執行不依賴於外部變數或"狀態",只依賴於輸入的引數,任何時候只要引數相同,引用函式所得到的返回值總是相同的。

注:變數的值是不可變的(immutable),也就是說不允許像指令式程式設計語言中那樣多次給一個變數賦值。比如說在指令式程式設計語言我們寫“x = x + 1”,這依賴可變狀態的事實,拿給程式設計師看說是對的,但拿給數學家看,卻被認為這個等式為假

三、意義

1.程式碼簡潔,開發快速。函數語言程式設計大量使用函式,減少了程式碼的重複,因此程式比較短,開發速度較快。Paul Graham黑客與畫家一書中寫道:同樣功能的程式,極端情況下,Lisp程式碼的長度可能是C程式碼的二十分之一。

2.接近自然語言,易於理解。add(1,2).multiply(3).subtract(4)

3.更方便的程式碼管理。函數語言程式設計不依賴、也不會改變外界的狀態,只要給定輸入引數,返回的結果必定相同。因此,每一個函式都可以被看做獨立單元,很有利於進行單元測試(unit testing)和除錯(debugging),以及模組化組合

4.易於“併發程式設計”。函數語言程式設計不需要考慮"死鎖"deadlock),因為它不修改變數,所以根本不存在""執行緒的問題。不必擔心一個執行緒的資料,被另一個執行緒修改,所以可以很放心地把工作分攤到多個執行緒,部署"併發程式設計"concurrency)。

5.程式碼的熱升級。函數語言程式設計沒有副作用,只要保

證介面不變,內部實現是外部無關的。所以,可以在執行狀態下直接升級程式碼,不需要重啟,也不需要停機。語言早就證明了這一點,它是瑞典愛立信公司為了管理電話系統而開發的,電話系統的升級當然是不能停機的。

四、特性

高階函式(Higher-order function)。高階函式就是引數為函式或返回值為函式的函式,有了高階函式,就可以將複用的粒度降低到函式級別。例子:

def sumInts(a: Int, b: Int): Int =
  if (a > b) 0 else a + sumInts(a + 1, b)

def sumCubes(a: Int, b: Int): Int =
  if (a > b) 0 else cube(a) + sumCubes(a + 1, b)

def sumFactorials(a: Int, b: Int): Int =
  if (a > b) 0 else fact(a) + sumFactorials(a + 1, b)

分別是求a到b之間整數之和,求a到b之間整數的立方和,求a到b之間整數的階乘和。

我們可以定義一個高階函式sum:
def sum(f: Int => Int, a: Int, b: Int): Int =
  if (a > b) 0
  else f(a) + sum(f, a + 1, b)
其中引數f是一個函式,在函式中呼叫f函式進行計算,並進行求和。

然後就可以寫如下的函式
def sumInts(a: Int, b: Int) = sum(id, a, b)
def sumCubs(a: Int, b: Int) = sum(cube, a, b)
def sumFactorials(a: Int, b: Int) = sum(fact, a, b)

def id(x: Int): Int = x
def cube(x: Int): Int = x * x * x
def fact(x: Int): Int = if (x == 0) 1 else fact(x - 1)

柯里化(Currying)。是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數而且返回結果的新函式的技術(函式式語言的表達能力很強。用這種語言程式設計的時候基本不需要設計模式),一種可以快速且簡單的實現函式封裝的捷徑

以介面卡模式為例,在設計模式中,介面卡模式(英語:adapter pattern)有時候也稱包裝樣式或者包裝(wrapper)。將一個類的介面轉接成使用者所期待的。一個適配使得因介面不相容而不能在一起工作的類能在一起工作,做法是將類自己的介面包裹在一個已存在的類中。

命令式:
int pow(int i, int j);
int square(int i)
{
    return pow(i, 2);
}
函式式:
square = int pow(int i, 2);

惰性求值(Lazy Evaluation)。是在將表示式賦值給變數(或稱作繫結)時並不計算表示式的值,而在變數第一次被使用時才進行計算。這樣就可以通過避免不必要的求值提升效能。

scala> val x = { println("x"); 15 }
x
x: Int = 15

scala> lazy val y = { println("y"); 13 }
y: Int = <lazy>

scala> y
y
res3: Int = 13

scala> y
res4: Int = 13

閉包(Closure)。所謂“閉包”,就是在建構函式體內定義另外的函式作為目標物件的方法函式,而這個物件的方法函式反過來引用外層函式體中的臨時變數。當函式a的內部函式b被函式a外的一個變數引用的時候,就建立了一個閉包。

Function makeIncrementer() {
   int n = 0;

   int increment() {
       return ++n;
   }
}

Function inc1 = makeIncrementer();
Function inc2 = makeIncrementer();

inc1(); // returns 1;
inc1(); // returns 2;
inc1(); // returns 3;
inc2(); // returns 1;
inc2(); // returns 2;
inc2(); // returns 3;


def makeIncrementer():Int={ var n = 0 def increment()={++n}}

計算續體(Continuation

我們對函式的理解只有一半是正確的,因為這樣的理解基於一個錯誤的假設:函式一定要把其返回值返回給呼叫者。按照這樣的理解,continuation就是更加廣義的函式。這裡的函式不一定要把返回值傳回給呼叫者,相反,它可以把返回值傳給程式中的任意程式碼。continuation就是一種特別的引數,把這種引數傳到函式中,函式就能夠根據continuation將返回值傳遞到程式中的某段程式碼中。應用於Pipeline管道