對於一個數學專業畢業的學生,函數語言程式設計天然的吸引力了我,從哥德爾的不完備定理,到邱奇的lambda演算,到柯里的組合子邏輯,無不吸引著我。 而swift作為一門多程式設計正規化的語言,同樣支援函數語言程式設計。 不過函數語言程式設計比較複雜,我也只是管中窺豹,談談自己在swift中的認識。
函式式的資料結構
函數語言程式設計和指令式程式設計不一樣,進行純函數語言程式設計,由於無法進行賦值操作(不可變的資料結構),而在C或者是C++這樣的語言中資料結構往往都是可變的,所以以前所學的資料結構和演算法都沒有什麼用。 由於資料結構的不同,禁止賦值,有額外的開銷,演算法也不一樣。
Binary Search Tree 插入演算法
常見的資料結構
public class TreeNode {
public var val: Int
public var left: TreeNode?
public var right: TreeNode?
public init(_ val: Int) {
self.val = val
self.left = nil
self.right = nil
}
}
複製程式碼
和它的插入演算法
func insertIntoBST(_ root: TreeNode?, _ val: Int) -> TreeNode? {
guard let rootNode = root else {
return TreeNode(val)
}
if val < rootNode.val {
rootNode.left = insertIntoBST(rootNode.left, val)
} else {
rootNode.right = insertIntoBST(rootNode.right, val)
}
return root
}
複製程式碼
用函式式的資料結構表示
indirect enum BST {
case leaf
case node(BST,Int,BST)
init() {
self = .leaf
}
init(_ value: Int) {
self = .node(.leaf,value,.leaf)
}
}
複製程式碼
同樣的,函式式的插入演算法
func insertIntoBST(_ root:BST, _ val:Int) -> BST {
switch root {
case .leaf:
return BST(val)
case let .node(left, value, right):
if val < value {
return BST.node(insertIntoBST(left, val), value, right)
} else {
return BST.node(left, value, insertIntoBST(right, val))
}
}
}
複製程式碼
可以看到由於不可變的資料結構,不能對樹做修改,要實現插入演算法,必須每次都建立新的樹。
一等函式
第一次接觸swift最大的印象就是函式是一等公民,可以作為引數傳遞。
比如下面這個fibF
func fib(_ n:Int) -> Int {
if n < 2 {
return n
} else {
return fib(n-1) + fib(n-2)
}
}
let fibF = fib
fibF(11)
複製程式碼
或者我們不想顯式的定義fib
這個函式,使用Z組合子
func Z<T,U>(f:@escaping ((T) -> U, T) -> U) -> (T) -> U {
return {(x:T) -> U in f(Z(f: f),x)}
}
let fibZ = Z(f: {$1 < 2 ? $1 : $0($1-1) + $0($1-2)})
fibZ(11)
複製程式碼
在系統提供的方法中也很常見,比如Array的sorted
方法就可以傳一個函式
[1,2,3,4,5].sorted(by: >)
複製程式碼
一等函式的概念源自邱奇的lambda演算。 現代計算機採用的是馮諾伊曼結構,而馮諾依曼結構是圖靈機的一個實現。然而在圖靈為了解決判定性問題引入圖靈機,和他同時代的天才,邱奇,在幾個月前用lambda演算和遞迴函式,證明了類似的論題。這三個模型(圖靈機,lambda演算和遞迴函式)計算能力等價(邱奇-圖靈猜想)。
運算元
高階函式 map與flatMap
什麼函數語言程式設計呢,可能很多人最大的印象就是使用map和flatMap這樣的高階函式,當然這只是一部分
在swift中Optionals
和Collection
有map和flatMap函式
map函式
(1...10).map({"\($0)"})
複製程式碼
flatMap函式
["Hi","Swift"].flatMap({$0})
複製程式碼
Functor與Monad
為什麼Optionals
和Array
會有同樣的名稱的方法?
這些map和flatMap方法是否遵守相同的邏輯?
我們可以看下swift中對Optionals
的map函式的定義
@inlinable public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
複製程式碼
對Array
的map函式定義
@inlinable public func map<T>(_ transform: (Bound) throws -> T) rethrows -> [T]
複製程式碼
Haskell中Functor
有個函式fmap
(swift中的map)
fmap :: (a -> b) -> [a] -> [b]
複製程式碼
所以它們被稱為Functor(函子)
相應的檢視一下swift中對flatMap的定義
//Optionals
@inlinable public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
//Array
@inlinable public func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence
複製程式碼
Haskell中Monad
對函式>>=
的定義(swift中的flatMap)
(>>=) :: m a -> (a -> m b) -> m b
複製程式碼
翻譯成swift就是(虛擬碼)
func flatMap<A,B>(x:F<A>)(_ transform: (A) -> F<B>) -> F<B>
複製程式碼
可見Optionals
和Array
都是Monad
物件導向與函式式
swift雖然支援函數語言程式設計,然而在實際開發的時候,純函式程式設計並非好的選擇,因為傳統的演算法和資料結構都是以圖靈機和過程式語言為基礎,而且由於資料的不可變性放棄了執行機可以反覆擦寫記憶體屬性,所以並不能做到高效的演算法,不能保證效能,但是好在swift是一門支援多程式設計正規化的語言,在合適的地方使用合適的方法才是我們需要去做的。
參考資料
本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。@白爾摩斯