Skip to content

Instantly share code, notes, and snippets.

@harlanhaskins
Forked from mbrandonw/1-Functor-and-Monad.md
Last active November 22, 2021 23:50
Show Gist options
  • Save harlanhaskins/dd31095330b4889a741c to your computer and use it in GitHub Desktop.
Save harlanhaskins/dd31095330b4889a741c 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
import Foundation
protocol Functor {
typealias A
typealias B
typealias FB
func fmap(A -> B) -> FB
}
protocol Monad: Functor {
static func unit(f: A) -> Self
func bind(f : A -> FB) -> FB
func >>=(x: Self, f : A -> FB) -> FB
}
infix operator >>= { associativity left }
func >>=<M: Monad>(x: M, f: M.A -> M.FB) -> M.FB {
return x.bind(f)
}
func bind<M: Monad>(x: M, f: M.A -> M.FB) -> M.FB {
return x.bind(f)
}
func unit<M: Monad>(a: M.A) -> M {
return M.unit(a)
}
/**
Make Array a functor
*/
extension Array: Functor {
typealias A = T
typealias B = Any
typealias FB = [B]
func fmap<B>(f: A -> B) -> [B] {
return self.map(f)
}
}
/**
Make Array a monad
*/
extension Array: Monad {
static func unit(x: A) -> [A] {
return [x]
}
func bind<B>(f: A -> [B]) -> [B] {
return self.map(f).reduce([], combine: +)
}
}
/**
Make optional a functor
*/
extension Optional: Functor {
typealias A = T
typealias B = Any
typealias FB = B?
func fmap<B>(f: A -> B) -> B? {
return self.map(f)
}
}
/**
Make optional a monad
*/
extension Optional: Monad {
static func unit(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: A -> B) -> String {
return "".join(self.characters.map { String(f($0) as! Character) })
}
}
extension String: Monad {
static func unit(c: A) -> String {
return String(c)
}
func bind(f: A -> FB) -> String {
return "".join(self.characters.map(f))
}
}
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.indexOf(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]
optionalXs.fmap { $0.fmap(square) }
let optional2: Double? = 2
optional2.fmap(test)
optional2.bind(squareRoot)
optional2 >>= { squareRoot($0) }
"hello world".fmap(rot13)
"hello world" >>= { unit(rot13($0)) }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment