Skip to content

Instantly share code, notes, and snippets.

@mbrandonw
Last active June 4, 2022 02:12
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save mbrandonw/42651182eddf53ca3991 to your computer and use it in GitHub Desktop.
Save mbrandonw/42651182eddf53ca3991 to your computer and use it in GitHub Desktop.
Swift Functor and Monad

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. However, it is very fragile (i.e. easy to crash the compiler).

For example, instance methods of fmap will run fine, but attempting to use a globally defined fmap that acts on Functor types will cause a crash. Similarly for bind. Unfortunately this means we cannot define the nice infix operator versions of these functions.

import Foundation
public struct K<A> {}
public protocol Functor {
typealias _A
typealias _B
typealias _FB = K<_B>
func fmap (_A -> _B) -> _FB
}
/**
Using this global fmap usually crashes :(
*/
public func fmap <F: Functor> (f: F._A -> F._B) -> F -> F._FB {
return { $0.fmap(f) }
}
public protocol Monad : Functor {
class func unit (f: _A) -> Self
func bind (f : _A -> _FB) -> _FB
func >>= (x: Self, f : _A -> _FB) -> _FB
}
/**
Using these global binds usually crashes :(
*/
infix operator >>= {associativity left}
public func >>= <M: Monad> (x: M, f: M._A -> M._FB) -> M._FB {
return x.bind(f)
}
public func bind <M: Monad> (x: M, f: M._A -> M._FB) -> M._FB {
return x.bind(f)
}
/**
Make Array a functor
*/
extension Array : Functor {
typealias _A = T
typealias _B = Any
typealias _FB = [_B]
public func fmap <_B> (f: _A -> _B) -> [_B] {
return self.map(f)
}
}
/**
Make Array a monad
*/
extension Array : Monad {
public static func unit (x: _A) -> [_A] {
return [x]
}
public func bind <_B> (f: _A -> [_B]) -> [_B] {
return self.map(f).reduce([], +)
}
}
/**
Make optional a functor
*/
extension Optional : Functor {
typealias _A = T
typealias _B = Any
typealias _FB = _B?
public func fmap <_B> (f: _A -> _B) -> _B? {
switch self {
case let .Some(value):
value
return f(value)
case .None:
return .None
}
}
}
/**
Make optional a monad
*/
extension Optional : Monad {
public static func unit (x: _A) -> _A? {
return Optional<_A>.Some(x)
}
public func bind <_B> (f: _A -> _B?) -> _B? {
switch self {
case let .Some(value):
return f(value)
case .None:
return .None
}
}
}
func square (x: Double) -> Double {
return x * x
}
func invert (x: Double) -> Double? {
if (x != 0.0) {
return 1.0 / x
}
return nil
}
func squareRoot (x: Double) -> Double? {
if (x < 0.0) {
return nil
}
return sqrt(x)
}
func test (x: Double) -> String {
return "test: \(x)"
}
/**
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)
// fmap(square)(xs) // crash!
let optional2: Double? = 2
optional2.fmap(test)
optional2.bind(squareRoot)
// optional2 >>= squareRoot // crash!
"done"
@jprider63
Copy link

What version of XCode are you running this on? With the following code (XCode 6.4) I get the error cannot invoke 'bind' with an argument list of type '((Int) -> Optional<Int>)'. Also, the playground doesn't seem to work as consistently for me.

protocol Functor {
    typealias _A
    typealias _B
    typealias _FB//  = K<_B>

    func fmap (_A -> _B) -> _FB
}

protocol Monad : Functor {
    static func unit (a: _A) -> Self
    func bind (f : _A -> _FB) -> _FB
}

extension Optional : Functor {
    typealias _A = T
    typealias _B = Any
    typealias _FB = Optional<_B>

    func fmap(f: _A -> _B) -> _FB {
        switch self {
        case .Some(let value):
            return f(value)
        case .None:
            return .None
        }
    }
}

extension Optional : Monad {
    static func unit(a: _A) -> Optional {
        return Optional.Some( a)
    }

    func bind(f: _A -> _FB) -> _FB {
        switch self {
        case .Some(let o):
            return f( o)
        case .None:
            return Optional.None
        }
    }
}

let s : Optional<Int> = Optional.Some(32)

let y = s.fmap( {$0 + 1})
println( s)
println( y)

let z = y.bind( {(z : Int) -> Optional<Int> in
    return Optional.unit(z - 63)
})
println( z)

@jprider63
Copy link

This fork is working for me: https://gist.github.com/harlanhaskins/dd31095330b4889a741c

What do you think about making a pod for this?

@mjwillson
Copy link

mjwillson commented Feb 13, 2018

In case anyone else is wondering why this works, note the following also works for fmap and is simpler -- the associatedtype B / typealias B = Any isn't actually necessary.

It's essentially relying on covariance of the functor -- [B] being a subtype of [Any], B? of Any? etc -- to allow a more specific return type to be inferred for fmap for specific calls, while still allowing those implementations to conform with the type of the protocol method.

protocol Functor {
  associatedtype A
  associatedtype FB

  func fmap(_ f: (A) -> Any) -> FB
}

extension Array: Functor {
  typealias A = Element
  typealias FB = [Any]

  func fmap<B>(_ f: (A) -> B) -> [B] {
    return self.map(f)
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment