Swift 函数式编程探索(1)—— Functor 和 Monad

最近在看了 swift 大会上由包涵卿大神带来的“Swift 函数式编程实践” 演讲之后,感触颇深,这几天搜索了一些 swift 函数式编程相关的文章了解了一下。这里做一点记录。
这里用到的代码我都写在了 playground 里,放到了 Github 上。

更新:发现解释的更好的大有人在,非常推荐看看:
唐巧大神写的这篇:Swift 烧脑体操(四) - map 和 flatMap
以及这篇:Swift 烧脑体操(五)- Monad
swift gg 翻译的这篇:Swift 2.0 :揭秘 Map 和 FlatMap

在这里记录一下自己的理解(需要阅读者对闭包、泛型等有一定理解)

什么是函数式编程?知乎上的这个回答非常详细:

http://zhihu.com/question/28292740/answer/40336090

而且像包大神说的,转换编程范式实际上是转换思维方式,对于函数式编程,还需要慢慢思考消化来理解。如果有时间,尝试学学 Haskell 来写个应用吧。

这里记录一下个人对 swift 函数式编程相关的一些 feature 的理解。

1. Functor —— 仿函数或函子

任何定义了 map (Haskell 中的 fmap)如何作用于自己的类型都是 Functor
使这个类有了类似于函数的行为,让这个类使用上像函数,称为仿函数
什么是 map 函数呢? map 函数请求一个将自身封装值转换为另一个未封装值的函数,并应用到自身。
Swift 中的 Optional、 SequenceTypes (如数组、字典) 都有 map 函数,所以可以说 swift 中的 optional,array 等都是 functor。

Optional 中的 map 方法

optional 中的的 map 方法是这样定义的:

/// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`.
public func map(@noescape f: (Wrapped) throws -> U) rethrows -> U?
// @noescape 标记一个闭包参数表示这个闭包参数不会被传递到其他没有 @noescape 标记闭包参数的方法中,比如其他线程

有了这样的 map 方法,我们可以这样使用:
将一个函数应用到封装内的内容中

let a = Optional.Some(2)
let b = Optional.None

let addOne: Int -> Int = { $0 + 1 }

a.map(addOne)  //返回一个 Optional(3)
b.map(addOne)  //返回一个 Optional.None
SequenceTypes 中的 map 方法

SequenceTypes 中的 map 方法是这样定义的:

/// Returns an `Array` containing the results of mapping `transform`
/// over `self`.
///
/// - Complexity: O(N).
@warn_unused_result
public func map(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]

我们能够这样使用:

let ints = [2]
let noInt : [Int] = []

ints.map(addOne)  //返回一个 [2]
noInt.map(addOne)  //返回一个 []
实践

使用 map 函数,能够不需要解开封装就将方法应用在 Functor 中
而且也可以很容易地自定义一个 functor,比如:

public enum Result {
    case success(Value)
    case failure(ErrorType)

    func map(@noescape transform: Value throws -> T) rethrows -> Result {
        switch self {
        case let .failure(error):
            return Result.failure(error)
        case let .success(value):
            return try Result.success(transform(value))
        }
    }
}

我们甚至还可以定义一个操作符号 <^> 来进行 map 操作(参考了 github 上的 Rune):

infix operator <^> { }

public func <^>(@noescape f: U throws -> T, a: U?) rethrows -> T? {
    return try a.map(f)
}

public func <^>(
    @noescape f: S.Generator.Element throws -> T, a: S) rethrows -> [T] {
    return try a.map(f)
}

public func <^>(@noescape f: U throws -> T, a: Result) rethrows -> Result {
    return a.map(f)
}

刚才的例子,我们就可以这么写,结果是和刚才一样的:

addOne <^> a
addOne <^> b

addOne <^> ints
addOne <^> noInt

2. Monad

任何实现了 flatmap (haskell 中的 liftM)的类型都是 monad
Monad 为封装的值,应用一个返回封装值的函数,也就是说, 传递进去的函数,是一个将封装内的值转化成同样封装值的函数,而所谓的 flatMap 中的 flat(flatten) 就是这里将这个函数返回的封装值解除封装,再放入自身的封装
有时间可以去看看 Monads for functional programming 这个 Philip Wadler 的 paper

optional 的 flatmap

optional 同样实现了 flatMap 所以 optional 是一个 monad

/// Returns `nil` if `self` is `nil`, `f(self!)` otherwise.
public func flatMap(@noescape f: (Wrapped) throws -> U?) rethrows -> U?

我们可以这样对一个 optional flatMap :

let half: Int -> Int? = { $0 % 2 == 1 ? nil : $0 / 2 }
let c = Optional.Some(3)
a.flatMap(half)  //结果为1
b.flatMap(half)  //结果为nil
c.flatMap(half)  //结果为nil
SequenceTypes 中的 flatMap

SequenceTypes 也是一个 Monad ,它的 flagMap 实现有两个:

/// Returns an `Array` containing the non-nil results of mapping
/// `transform` over `self`.
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
///   and *N* is the length of the result.
@warn_unused_result
public func flatMap(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]

这个 flatMap 会自动将 transform 返回的值为 nil 的数给剔除,可以像这样使用:

let singulars = [1, 3, 5, 7]
ints.flatMap(half)  //结果为 1
singulars.flatMap(half)  //结果为 []

SequenceTypes 的另一个 flatMap 是这样的:

/// Returns an `Array` containing the concatenated results of mapping
/// `transform` over `self`.
///
///     s.flatMap(transform)
///
/// is equivalent to
///
///     Array(s.map(transform).flatten())
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
///   and *N* is the length of the result.
@warn_unused_result
public func flatMap(transform: (Self.Generator.Element) throws -> S) rethrows -> [S.Generator.Element]
//注意这里的 transform 是没有 @noescape 标记的

这个 flatMap 会将一个生成一个数组的函数的返回数组解开封装,再返回,可以这样使用:

let plusTwoAndThree: Int -> [Int] = { [$0 * 2, $0 * 3] }
singulars.flatMap(plusTwoAndThree)  //返回 [2, 3, 6, 9, 10, 15, 14, 21]
实践

同样在自定义的类中也可以实现 flatMap:

extension Result {
    func flatMap(@noescape transform: Value throws -> Result) rethrows -> Result {
        switch self {
        case let .failure(error):
            return .failure(error)
        case let .success(value):
            return try transform(value)
        }
    }
}

当然,我们也可以定义一个 >>--<< 操作符来使用 flatMap

infix operator  >>- { associativity left }

public func >>-  (a : U? , @noescape f: U throws -> T?) rethrows -> T? {
    return try a.flatMap(f)
}
public func >>- (
    a: S, @noescape f: S.Generator.Element throws -> T?) rethrows -> [T] {
    return try a.flatMap(f)
}
public func >>- (
    a: S, f: S.Generator.Element throws -> S) rethrows -> [S.Generator.Element] {
    return try a.flatMap(f)
}
public func >>- (
    a: Result, @noescape f: U throws -> Result) rethrows -> Result {
    return try a.flatMap(f)
}

infix operator  -<< { associativity right }

public func -<<  (@noescape f: U throws -> T?, a : U?) rethrows -> T? {
    return try a.flatMap(f)
}
public func -<< (
     @noescape f: S.Generator.Element throws -> T?, a: S) rethrows -> [T] {
    return try a.flatMap(f)
}
public func -<< (
    f: S.Generator.Element throws -> S, a: S) rethrows -> [S.Generator.Element] {
    return try a.flatMap(f)
}
public func -<< (
    @noescape f: U throws -> Result, a: Result) rethrows -> Result {
    return try a.flatMap(f)
}

是不是有点眼花缭乱?我们怎么来使用这几个函数呢:

a >>- half
b >>- half
c >>- half

ints >>- half
singulars >>- half

singulars >>- plusTwoAndThree

或者

half -<< a
half -<< b
half -<< c

half -<< ints
half -<< singulars

plusTwoAndThree -<< singulars

两种和上面的都是一样的

其他实践

这里使用包涵卿大神的例子:
比如这里有4个方法

///处理一个网络请求回来的 Data ,返回一个封装了 UIImage 的一个 Result Type
func toImage(data: NSData) -> Result
///给这个图片加上一个 Alpha 值
func addAlpha(image: UIImage) -> Result
///给这个图片切一个圆角
func roundCorner(image: UIImage) -> Result
///加一个模糊效果
func applyBlur(image: UIImage) -> Result

这样的一个 monad 允许链式的编程,能够很容易使用,如下:

toImage(data)
    .flatMap(addAlpha)
    .flatMap(roundCorner)
    .flagMap(applyBlur)

或者运用一下运算符 >>-

toImage(data) >>- addAlpha >>- roundCorner >>- applyBlur

其实还有别的例子,下一篇,我就记录一下 functor 和 monad 的实际使用场景。

你可能感兴趣的:(Swift 函数式编程探索(1)—— Functor 和 Monad)