Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@sritchie
Created June 9, 2014 02:57
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sritchie/27bdf9329027d5cdf02e to your computer and use it in GitHub Desktop.
Save sritchie/27bdf9329027d5cdf02e to your computer and use it in GitHub Desktop.
Typeclasses in Swift
// Playground - noun: a place where people can play
import Cocoa
var str = "Hello, playground"
// Here's take 1. First, I defined the algebra like I would in
// Scala, as separate protocols:
protocol Semigroup {
typealias T
func plus(a: T, _ b: T) -> T
}
// And the monoid extension:
protocol Monoid: Semigroup {
func zero() -> T
}
// The problem here is you end up with these separate instances
// that implement the typeclasses. Because Swift has no implicit
// resolution, you have to go through some gymnastics to build on the
// typeclass.
class IntMonoid: Monoid {
typealias T = Int
func plus(a: Int, _ b: Int) -> Int {
return a + b
}
func zero() -> Int {
return 0
}
}
// I wrote this protocol that the types themselves could extend;
// the idea was that they could use the Monoid implementations to
// implement the functions. This is a sad but of duplication.
//
// This protocol allows you to call plus directly on an instance.
protocol Addable {
func zero() -> Self
func plus(x: Self) -> Self
}
// And an example of how to implement Addable for Int:
extension Int: Addable {
var monoid: IntMonoid { return IntMonoid() }
func zero() -> Int {
return monoid.zero()
}
func plus(x: Int) -> Int {
return monoid.plus(self, x);
}
}
// It works!!
1.plus(15)
// I can't quite get this to work (the error is "missing argument
// for parameter #1 in call"), but it doesn't matter. It's clearly
// too much duplication.
/**
// This is commented out because it screws up the playground.
func sum1<T : Addable>(array: T[]) -> T {
return array.reduce(T.zero(), combine: { $0.plus($1) })
}
**/
// For take 2, I took the approach from this post:
// http://slashmesays.tumblr.com/post/87833542239/type-classes-in-swift
protocol Semigroup2 {
class func plus(a: Self, _ b: Self) -> Self
}
// And the monoid extension:
protocol Monoid2: Semigroup2 {
class func zero() -> Self
}
// This allows you to extend Int directly:
extension Int: Monoid2 {
static func zero() -> Int {
return 0
}
static func plus(a: Int, _ b: Int) -> Int {
return a + b
}
}
// Addition is now a class method.
Int.plus(1, 2)
// And you can write global aliases for these things that LOOK
// like they're doing implicit resolution. This works because the
// generic type is aliased to the resolved, specific type within
// the body of the function:
func plus<T: Semigroup2>(a: T, b: T) -> T {
return T.plus(a, b)
}
// This works, now:
plus(1, 2)
// And you can build on top of the lower level functions:
func sum2<T : Monoid2>(array: T[]) -> T {
return array.reduce(T.zero(), combine: { plus($0, $1)})
}
// This works great for integers, and for anything else
// that implements the typeclass:
sum2([1, 2, 3])
@ChrisBuchholz
Copy link

I was toying with typeclasses in swift and then found your gist. Great to see others have been playing around with this too!

This is what I got:

protocol Eq {
    func eq(other: Self) -> Bool
    func inEq(other: Self) -> Bool
}

extension Int: Eq {
    func eq(other: Int) -> Bool { return self == other }
    func inEq(other: Int) -> Bool { return !self.eq(other) }
}

extension String: Eq {
    func eq(other: String) -> Bool { return self == other }
    func inEq(other: String) -> Bool { return !self.eq(other) }
}

infix operator ==== { associativity left precedence 90 }
func ====<T: Eq>(left: T, right: T) -> Bool {
    return left.eq(right)
}

infix operator /=== { associativity left precedence 90 }
func /===<T: Eq>(left: T, right: T) -> Bool {
    return left.inEq(right)
}

let a: Int = 10
let b: Int = 10
let c: Int = 20

a ==== b // true
a /=== b // false
a ==== c // false
a /=== c // true

let d: String = "this"
let e: String = "this"
let f: String = "that"

d ==== e // true
d /=== e // false
d ==== f // false
d /=== f // true

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