函数式编程-将Monad(单子)融入Swift
前言近期又开始折腾起Haskell,掉进这个深坑恐怕很难再爬上来了。在不断深入了解Haskell的各种概念以及使用它们去解决实际问题的时候,我会试想着将这些概念移植到Swift中。函数式编程范式的很多概念在Swift等主打面向对象范式的语言中就像各种设计模式一样,优雅地帮助我们构建好整个项目,促使我们的代码更加的美观优雅、安全可靠。 关于Monad、在Swift中实现Monad的一些见解Monad回顾在上一篇文章《函数式编程-一篇文章概述Functor(函子)、Monad(单子)、Applicative》中提到过,我们可以将一个值用 // 扩展Optional,实现bind方法 extension Optional { func bind<O>(_ f: (Wrapped) -> Optional<O>) -> Optional<O> { switch self { case .none: return .none case .some(let v): return f(v) } } } // 定义bind运算符`>>-` precedencegroup Bind { associativity: left higherThan: DefaultPrecedence } infix operator >>- : Bind func >>- <L,R>(lhs: L?,rhs: (L) -> R?) -> R? { return lhs.bind(rhs) } // 除法,若除数为0,返回nil // 方法类型: // A B C // (Double) -> (Double) -> Double? // 用B除以A func divide(_ num: Double) -> (Double) -> Double? { return { guard num != 0 else { return nil } return $0 / num } } let ret = divide(2)(16) >>- divide(3) >>- divide(2) // 1.33333333... // 可以写成 // let ret = Optional.some(16) >>- divide(2) >>- divide(3) >>- divide(2) let ret2 = Optional.some(16) >>- divide(2) >>- divide(0) >>- divide(2) // nil
如上,我将Swift中的 Swift中实现Monad
Swift中的协议无法定义出MonadHaskell中,Monad的定义为: class Applicative m => Monad (m :: * -> *) where (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b return :: a -> m a fail :: String -> m a
Haskell的类型类与Swift中的协议类似,我们可以看到第一行声明了Monad,而 protocol Monad {
associatedtype MT
func bind(_ f: (MT) -> Self) -> Self }
像上面定义的Monad协议,泛型参数为 Swift中无法优雅地解决Monad中的lambda嵌套Haskell的 main = do first <- getLine second <- getLine putStr $ first ++ second
对于Swift来说,若我们在使用 let one: Int? = 4 let two: Int? = nil let three: Int? = 7 let result1 = one >>- { o in two >>- { t in o + t } } let result2 = one >>- { o in two >>- { t in three >>- { th in o * t * th } } }
如果Swift支持 let result1 = do { o <- one t <- two th <- three return o * t * th }
上面的语法纯属脑补。 所以一般来说应该不会用Swift去实现某些需要多重嵌套lambda的Monad。 Either Monad在上一篇函数式编程的文章中有提到 enum Either<L,R> { case left(L) case right(R) } extension Either { static func ret(_ data: R) -> Either<L,R> { return .right(data) } func bind<O>(_ f: (R) -> Either<L,O>) -> Either<L,O> { switch self { case .left(let l): return .left(l) case .right(let r): return f(r) } } } func >>- <L,R,O> (lhs: Either<L,R>,f: (R) -> Either<L,O> { return lhs.bind(f) }
什么是上下文所包含的数据类型,什么是值的类型? 用 typealias EitherOne<T> = Either<ErrorOne,T> typealias EitherTwo<T> = Either<ErrorTwo,T>
从上面的代码我们也可以看出,Swift也能像Haskell一样对类型构造器(泛型类)进行柯里化操作,意思是我们在实现一个泛型的时候无需把它需要的所有泛型参数都填满,可以只填入其中的若干个。 Writer monad为了引入
对于这个需求,传统的做法可能是在全局中保存着档案记录,每当任务完成后,我们就响应地修改这个全局档案,直到所有任务完成。
Monoid在继续深入 对于一个集合,存在一个二元运算:
举个例子: 我们可以在Swift中定义Monoid的协议: // 单位半群 protocol Monoid { typealias T = Self static var mEmpty: T { get } func mAppend(_ next: T) -> T }
其中, 上面的例子就可以在Swift中这样实现: struct Sum { let num: Int } extension Sum: Monoid { static var mEmpty: Sum { return Sum(num: 0) } func mAppend(_ next: Sum) -> Sum { return Sum(num: num + next.num) } }
我们使用 struct Product { let num: Int } extension Product: Monoid { static var mEmpty: Product { return Product(num: 1) } func mAppend(_ next: Product) -> Product { return Product(num: num * next.num) } }
上面这个单位半群的二元运算就是乘法运算,所以单位元为 像布尔类型,可以引出两种Monoid: struct All { let bool: Bool } extension All: Monoid { static var mEmpty: All { return All(bool: true) } func mAppend(_ next: All) -> All { return All(bool: bool && next.bool) } } struct `Any` { let bool: Bool } extension `Any`: Monoid { static var mEmpty: `Any` { return `Any`(bool: true) } func mAppend(_ next: `Any`) -> `Any` { return `Any`(bool: bool || next.bool) } }
当我们要判断一组布尔值是否都为 let values = [true,false,true,false] let result1 = values.map(`Any`.init) .reduce(`Any`.mEmpty) { $0.mAppend($1) }.bool // true let result2 = values.map(All.init) .reduce(All.mEmpty) { $0.mAppend($1) }.bool // false
实现Writer monad下面继续来深入 // Writer
struct Writer<W,T> where W: Monoid { let data: T let record: W } extension Writer{ static func ret(_ data: T) -> Writer<W,T> { return Writer(data: data,record: W.mEmpty) } func bind<O>(_ f: (T) -> Writer<W,O>) -> Writer<W,O> { let newM = f(data) let newData = newM.data let newW = newM.record return Writer<W,O>(data: newData,record: record.mAppend(newW)) } } func >>- <L,W>(lhs: Writer<W,L>,rhs: (L) -> Writer<W,R>) -> Writer<W,R> where W: Monoid { return lhs.bind(rhs) }
分析下实现的源码:
Demo接下来我们用 首先我们先让 extension String: Monoid { static var mEmpty: String { return "" } func mAppend(_ next: String) -> String { return self + next } }
这个针对 这里我为 typealias MWriter = Writer<String,Double>
然后定义 func add(_ num: Double) -> (Double) -> MWriter {
return { MWriter(data: $0 + num,record: "加上(num) ") } } func subtract(_ num: Double) -> (Double) -> MWriter { return { MWriter(data: $0 - num,record: "减去(num) ") } } func multiply(_ num: Double) -> (Double) -> MWriter { return { MWriter(data: $0 * num,record: "乘以(num) ") } } func divide(_ num: Double) -> (Double) -> MWriter { return { MWriter(data: $0 / num,record: "除以(num) ") } }
注意,这些函数都是高阶函数,若他们的形参跟返回值看成是 现在我们来测试一下: let resultW = MWriter.ret(1) >>- add(3) >>- multiply(5) >>- subtract(6) >>- divide(7) let resultD = resultW.data // 2.0 let resultRecord = resultW.record // "加上3.0 乘以5.0 减去6.0 除以7.0"
可见,我们得到了多次连续运算后的结果 当然, typealias ScoreWriter = Writer<All,Int>
func append(_ score: Int) -> (Int) -> ScoreWriter {
return { ScoreWriter(data: $0 + score,record: All(bool: score >= 60)) } } let allScores = [45,60,98,77,65,59,86,93] let result = allScores.reduce(ScoreWriter.ret(0)) { $0 >>- append($1) } let resultBool = result.record.bool // false let resultScore = result.data // 643
如果你把 // 这里我用反单引号(`)将Any包裹住,因为Any为Swift中的关键字
typealias ScoreWriter = Writer<`Any`,record: `Any`(bool: score >= 60)) } } let allScores = [45,93] let result = allScores.reduce(ScoreWriter.ret(0)) { $0 >>- append($1) } let resultBool = result.record.bool // true
State Monad对于Swift来说,由于其不是纯函数式编程语言,所以也不会存在数据不可变的情况,我们可以随时用
首先我们来实现 struct State<S,T> {
let f: (S) -> (T,S) } extension State { static func ret(_ data: T) -> State<S,T> { return State { s in (data,s) } } func bind<O>(_ function: @escaping (T) -> State<S,O>) -> State<S,O> { let funct = f return State<S,O> { s in let (oldData,oldState) = funct(s) return function(oldData).f(oldState) } } } func >>- <S,T,O>(lhs: State<S,T>,f: @escaping (T) -> State<S,O> { return lhs.bind(f) }
如果某项操作需要状态,我们不想在作用域中创建一个新的变量来记录某些临时的状态,并随着操作的进行而改变,可以在每次进行操作完后把新的状态返回,这样,我们下一次操作就可以利用新的状态进行,以此类推。 下面我举一个使用 现假设现在服务器提供API,通过用户的ID可以获取到用户的名字,我们想要获取连续ID的n个用户的名字,并将这些名字包裹在一个数组中。 struct Person {
let id: Int let name: String } let data = ["Hello","My","Name","Is","Tangent","Haha"].enumerated().map(Person.init) func fetchNameWith(id: Int) -> String? { return data.filter { $0.id == id }.first?.name }
服务器提供 我们定义用于解决此问题的 typealias MState = State<Int,[String]>
func fetch(names: [String]) -> MState {
return MState { id in guard let name = fetchNameWith(id: id) else { return (names,id) } return (names + [name],id + 1) } }
这里需考虑一个边界情况,当服务器找不到指定的用户时,返回 下面来测试一下: let fetchFunc = MState.ret([]) >>- fetch >>- fetch >>- fetch >>- fetch let namesAndNextID = fetchFunc.f(1) let names = namesAndNextID.0 // ["My","Tangent"] let nextID = namesAndNextID.1 // 5
我们一开始把一个空的数组包裹到 总结本文概述了有关 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |