Skip to content

Instantly share code, notes, and snippets.

@merkury
Forked from harlanhaskins/1-Functor-and-Monad.md
Last active November 21, 2016 14:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save merkury/f97bf5f6dcaaacb48e6fdfcd6f2c4d07 to your computer and use it in GitHub Desktop.
Save merkury/f97bf5f6dcaaacb48e6fdfcd6f2c4d07 to your computer and use it in GitHub Desktop.

Copy and paste the swift code below into a playground to experiment.

This is a very close emulation of Functor and Monad typeclasses in swift. As of Swift 1.2 and Xcode 6.3, this is no longer very fragile.

Unfortunately, the compiler cannot verify the types when passing a function to (>>=). We have to wrap the function in a closure and call it with an explicit argument to compile.

optionalDoubles >>= squareRoot // doesn't compile
optionalDoubles >>= { squareRoot($0) } // compiles
//: Playground - noun: a place where people can play
import UIKit
protocol Functor {
associatedtype A
associatedtype B
associatedtype FB
func fmap(_: @escaping (A) -> B) -> FB
}
protocol Monad: Functor {
static func unit(f: A) -> Self
func bind(f : (A) -> FB) -> FB
static func >>=(x: Self, f : (A) -> FB) -> FB
}
infix operator >>=: AdditionPrecedence //{associativity left}
func >>=<M: Monad>(x: M, f: (M.A) -> M.FB) -> M.FB {
return x.bind(f: f)
}
func bind<M: Monad>(x: M, f: (M.A) -> M.FB) -> M.FB {
return x.bind(f: f)
}
func unit<M: Monad>(a: M.A) -> M {
return M.unit(f: a)
}
/**
Make Array a functor
*/
extension Array: Functor {
typealias A = Element
typealias B = Any
typealias FB = [B]
func fmap<B>(_ f: @escaping (A) -> B) -> [B] {
return self.map(f)
}
}
/**
Make Array a monad
*/
extension Array: Monad {
static func unit(f: Element) -> [Element] {
return [f]
}
func bind(f: (Element) -> Array<Array.B>) -> Array<Array.B> {
let addTwo: ([Array.B], [Array.B]) -> [Array.B] = { x, y in x + y }
return self.map(f).reduce([], addTwo)
}
}
/**
Make optional a functor
*/
extension Optional: Functor {
typealias A = Wrapped
typealias B = Any
typealias FB = B?
func fmap<B>(_ f: @escaping (A) -> B) -> B? {
return self.map(f)
}
}
/**
Make optional a monad
*/
extension Optional: Monad {
static func unit(f x: A) -> A? {
return Optional<A>.some(x)
}
func bind<B>(f: (A) -> B?) -> B? {
return self.flatMap(f)
}
}
extension String: Functor {
typealias A = Character
typealias B = Character
typealias FB = String
func fmap<B>(_ f: @escaping (A) -> B) -> String {
return (self.characters.map { String(f($0) as! Character) }).joined()
}
}
extension String: Monad {
static func unit(f c: A) -> String {
return String(c)
}
func bind(f: (A) -> FB) -> String {
return (self.characters.map(f)).joined()
}
}
func square(x: Double) -> Double {
return x * x
}
func invert(x: Double) -> Double? {
return fabs(x) > 0.0 ? 1.0 / x : nil
}
func squareRoot(x: Double) -> Double? {
return x > 0.0 ? sqrt(x) : nil
}
func test(x: Double) -> String {
return "test: \(x)"
}
let lettersArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz".characters.map { $0 }
func rot13(input: Character) -> Character {
if let i = lettersArray.index(of: input) {
return lettersArray[i + 13 % Int(lettersArray.count)]
} else {
return input
}
}
/**
Let's take Functor and Monad out for a spin...
*/
let xs = [2.0, 3.0, 5.0, 7.0, 11.0, 13.0, 17.0]
xs.fmap(square)
let optionalXs: [Double?] = [2.0, nil, 5.0, 7.0, 11.0, 13.0, 17.0]
let squared = optionalXs.fmap { $0.fmap(square) }
print (squared)
let optional2: Double? = 2
optional2.fmap(test)
optional2.bind(f: squareRoot)
optional2 >>= { squareRoot(x: $0) }
"hello world".fmap(rot13)
"hello world" >>= { unit(a: rot13(input: $0)) }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment