上次了解了一点函数式编程之后,学习了一下《Funtional Swift》 这本书,仿佛打开了新世界的大门。
一直看文章不如自己实践,于是我尝试在项目中使用了一下,现在对 monad、functor 等已经相对熟悉理解了,并且发现这个范式有着非常方便的一面。
这里用到的代码我都写在了 playground 里,放到了 Github 上。
这里就分享一下我在最近的实际项目中是如何使用函数式编程的:
1. 使用 monad(flagMap)处理异常
上一篇函数式编程中说到,定义一个 functor 和 monad 非常容易,只要定义了 map 函数和 flatMap 函数就可以。
使用 flatMap 有非常方便的错误处理能力,比如说使用 monad Result:
public enum Result<Value> {
case success(Value)
case failure(ErrorType)
public func flatMap<T>( @noescape transform: Value throws -> Result<T>) rethrows -> Result<T> {
switch self {
case let .failure(error):
return .failure(error)
case let .success(value):
return try transform(value)
}
}
}
假设我们要将一个从 urlsession 获得的data 数据转换为 json 格式,我们定义一个从 nsdata 转为 json 的的方法
//注意这里使用了 type alias ,较为方便
typealias JSON = [String : AnyObject]
func toJSON(data: NSData) -> Result<[String : AnyObject]> {
do {
if let json = try NSJSONSerialization.JSONObjectWithData(
data, options: []) as? JSON {
return .success(json)
}
return .success([ : ])
} catch let error {
return .failure(error)
}
}
当我们获得了一个类型为 Result<NSData> 的result时候,我们可以这样:
let json = result.flatMap(form)
//如果成功,json 包含 JSON,如果失败,则是包含 Error
还可以更方便一点,我们使用上一篇定义的操作符>>-
:
我们就能这么写:
let json = result >>= form
不过,我们有可能会经常在一个形如someF<U , T>(a : U) -> Result<T>中重复写类似 form 函数一样的do catch。
所以有没有更好的解决办法?
2. 使用 functor(map)处理异常
其实也是有的,我们使用map。
extension Result{
public func map<T>( @noescape transform: Value throws -> T) rethrows -> Result<T> {
switch self {
case let .failure(error):
return Result<T>.failure(error)
case let .success(value):
return try Result<T>.success(transform(value))
}
}
//因为已经有了一个 rethrows 的 map,所以这里不能取名 map,使用 mapError
public func mapError<T>( @noescape transform: Value throws -> T) -> Result<T> {
do {
return try self.map(transform)
} catch let error {
return .failure(error)
}
}
}
我们写一个会抛出异常的函数,这么写,直接将之抛出:
public func toJSONThrows(data: NSData) throws -> JSON {
if let json = try NSJSONSerialization.JSONObjectWithData(
data, options: []) as? JSON {
return json
}
return [ : ]
}
当我们获得了一个类型为 Result<NSData> 的result时候,我们可以这样:
let json = result.mapError(toJSONThrows)
//如果成功,json 包含 JSON,如果失败,则是包含 Error,和faltMap一样
这样一来,任何可能会抛出错误的函数都使用 mapError,就能很方便地进行错误处理了。