淺談Swift中的函式式

升級之路發表於2019-04-11

對於一個數學專業畢業的學生,函數語言程式設計天然的吸引力了我,從哥德爾的不完備定理,到邱奇的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中OptionalsCollection 有map和flatMap函式 map函式

(1...10).map({"\($0)"}) 
複製程式碼

flatMap函式

["Hi","Swift"].flatMap({$0})
複製程式碼

Functor與Monad

為什麼OptionalsArray會有同樣的名稱的方法? 這些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>
複製程式碼

可見OptionalsArray都是Monad

物件導向與函式式

swift雖然支援函數語言程式設計,然而在實際開發的時候,純函式程式設計並非好的選擇,因為傳統的演算法和資料結構都是以圖靈機和過程式語言為基礎,而且由於資料的不可變性放棄了執行機可以反覆擦寫記憶體屬性,所以並不能做到高效的演算法,不能保證效能,但是好在swift是一門支援多程式設計正規化的語言,在合適的地方使用合適的方法才是我們需要去做的。

參考資料

純函式資料結構

函式式 Swift

康托爾、哥德爾、圖靈——永恆的金色對角線

函數語言程式設計的早期歷史

Functor、Applicative 和 Monad

Haskell

本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。@白爾摩斯

相關文章