Created
April 25, 2017 17:05
-
-
Save loganmoseley/e2e05376fe63063df32f4a6ffbb283fb to your computer and use it in GitHub Desktop.
Type erase fmap to make a Functor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: Compose | |
func compose<A,B,C>(_ f: @escaping (B) -> C, after g: @escaping (A) -> B) -> ((A) -> C) { | |
return { f(g($0)) } | |
} | |
// MARK: Functor | |
/// The FunctorType protocol is used for types that can be mapped over. Types conforming to | |
/// FunctorType must implement a Functor constructor which must satisfy the following laws: | |
/// | |
/// fmap id == id | |
/// fmap (f . g) == fmap f . fmap g | |
/// | |
protocol FunctorType {} | |
typealias Fmap<A, B, FA: FunctorType, FB: FunctorType> = (@escaping (A) -> B) -> (FA) -> FB | |
struct Functor<A, B, FA: FunctorType, FB: FunctorType> { | |
let fmap: Fmap<A, B, FA, FB> | |
} | |
// MARK: Swift stdlib | |
extension Optional: FunctorType { | |
static func asFunctor<U>() -> Functor<Wrapped, U, Optional<Wrapped>, Optional<U>> { | |
return Functor { f in | |
return { fa in | |
let x = fa.map(f) | |
return x // Middleman `x` is required to avoid two compiler errors. Weird. | |
} | |
} | |
} | |
} | |
extension Array: FunctorType { | |
static func asFunctor<U>() -> Functor<Element, U, Array<Element>, Array<U>> { | |
return Functor { f in | |
return { fa in | |
return fa.map(f) | |
} | |
} | |
} | |
} | |
// MARK: - Laws | |
func id<T> (_ x: T) -> T { return x } | |
func increment(_ x: Int) -> Int { return x + 1 } | |
func double (_ x: Int) -> Int { return x * 2 } | |
// MARK: Optional proof | |
let x1 = 42 as Int? | |
let lhs1 = Optional.asFunctor().fmap( compose(increment, after: double) ) | |
let rhs1 = compose ( | |
Optional.asFunctor().fmap(increment), | |
after: Optional.asFunctor().fmap(double) | |
) | |
let identity1 = id(x1) == Optional.asFunctor().fmap(id)(x1) | |
let composition1 = lhs1(x1) == rhs1(x1) | |
// MARK: Array proof | |
let x2 = [1,2,3] | |
let lhs2 = Array.asFunctor().fmap( compose(increment, after: double) ) | |
let rhs2 = compose( | |
Array.asFunctor().fmap(increment), | |
after: Array.asFunctor().fmap(double) | |
) | |
let identity2 = id(x2) == Array.asFunctor().fmap(id)(x2) | |
let composition2 = lhs2(x2) == rhs2(x2) | |
// MARK: - Examples | |
// MARK: Use a functor directly. | |
let f = Optional.asFunctor().fmap { (x: Int) -> String in | |
return String(x) | |
} | |
let a = f(42) | |
func intToString(_ x: Int) -> String { | |
return String(x) | |
} | |
let g = Optional.asFunctor().fmap(intToString) | |
let b = g(42) | |
Optional.asFunctor().fmap(intToString)(42) | |
// MARK: Pass a functor into a function. | |
func double<FA, FB>(_ functor: Functor<Int, Int, FA, FB>) -> (FA) -> FB { | |
return functor.fmap { $0 * 2 } | |
} | |
double(Optional.asFunctor())(42) // 84: Int? | |
double(Array.asFunctor())([1,2,3]) // [2,4,6]: [Int] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment