Skip to content

Instantly share code, notes, and snippets.

@loganmoseley
Created April 25, 2017 17:05
Show Gist options
  • Save loganmoseley/e2e05376fe63063df32f4a6ffbb283fb to your computer and use it in GitHub Desktop.
Save loganmoseley/e2e05376fe63063df32f4a6ffbb283fb to your computer and use it in GitHub Desktop.
Type erase fmap to make a Functor
// 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