Swift3で継続モナドを書いてみる


Swift3で継続モナドを書いてみました。

contMonad.swift

import Foundation

typealias CPSCallBack<R> = (R)->Void
typealias CPSFunction<R> = (@escaping CPSCallBack<R>)->Void


class ContMonad<R> {

    let cps: CPSFunction<R>

    init(cps:@escaping CPSFunction<R>) {
        self.cps = cps
    }

    func run(callBack:@escaping CPSCallBack<R>)->Void {
        return cps(callBack)
    }

    func fmap<T>(_ f:@escaping (R)->T) -> ContMonad<T>{
        return self >>= {(r:R) in unit(f(r))}
    }
}

func >>=<R,T>(left:ContMonad<R>, right:@escaping (R)->ContMonad<T>) -> ContMonad<T> {


    let f = {(cb:@escaping CPSCallBack<T>) -> Void in
         left.run(callBack:{r in
            let c:ContMonad<T> = right(r)
            c.run(callBack:{t in
                cb(t)
            })
        })
    }

    return ContMonad<T>(cps:f)
}

func unit<R>(_ x:R) -> ContMonad<R> {

    let cps:(CPSCallBack<R>)->Void = {(cb:CPSCallBack<R>)->Void in
        return cb(x)
    }

    return ContMonad<R>(cps:cps)
}


func fmap<R,T>(f:@escaping (R) -> T, m:ContMonad<R>) -> ContMonad<T> {

    return m >>= {(r:R) in unit(f(r))}
}

func fmap<R,T>(f:@escaping (R)->T) -> (ContMonad<R>) -> ContMonad<T> {
    return {m in fmap(f:f, m:m)}
}


enum CPSResult<X>{
    case just(X)
    case error(Error)
}

使用例として、IDを非同期で取得する関数、IDから非同期でレコードを取得する関数、結果の変換をする関数を定義し、最後に>>=つまりバインドでつなげます。

example.swift


func getRecordCont(uid:SomeUID?) -> ContMonad<CPSResult<SomeRecord>> {
    return ContMonad{cb in
        let cb_ = cb
        DispatchQueue.global().async {
            let r = SomeRecord(title: "\(uid?.uid) - record")
            cb_(CPSResult.just(r))
        }
    }
}

func getUIDsCont()->ContMonad<CPSResult<SomeUID>> {
    return ContMonad { cb in
        DispatchQueue.global().async {
            let r = SomeUID(uid: "PO")
            cb(CPSResult.just(r))
        }
    }
}

func uidFromResult(_ r:CPSResult<SomeUID>) -> SomeUID? {
    switch r {
    case let .just(uid):
        return uid
    default:
        return nil
    }
}


var b = getUIDsCont().fmap(uidFromResult)
let c = b >>= getRecordCont

c.run { r in
    print("ContMonad result = \(r)")
}

sleep(100)

まとめたものはこちらに
https://github.com/KatagiriSo/RDMonad/blob/master/RDMonad