Skip to content

Instantly share code, notes, and snippets.

@truizlop
Created April 22, 2018 08:48
Show Gist options
  • Save truizlop/15f1b70cda369f454a7c073870a99152 to your computer and use it in GitHub Desktop.
Save truizlop/15f1b70cda369f454a7c073870a99152 to your computer and use it in GitHub Desktop.
Typeclasses exploration
import Foundation
class HK<F, A> {}
typealias HK2<F, A, B> = HK<HK<F, A>, B>
func id<A>(_ a : A) -> A {
return a
}
infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f : @escaping (A) -> B, _ g : @escaping (B) -> C) -> (A) -> C {
return { a in g(f(a)) }
}
// We are forced to declare A as an associated type to functor. Otherwise, it is not
// possible to bind it to the generic type of the implementation.
// Besides, it is not possible to specify that the implementer needs to be of type
// HK<F, A>, although the associated types (F, A) almost force you to do it.
protocol Functor {
associatedtype F
associatedtype A
func map<B>(_ f : @escaping (A) -> B) -> HK<F, B>
}
class ForMaybe {}
typealias MaybeOf<A> = HK<ForMaybe, A>
class Maybe<V> : MaybeOf<V> {
static func some(_ a : V) -> Maybe<V> {
return Some(a)
}
static func none() -> Maybe<A> {
return None()
}
func fold<B>(_ ifPresent : (A) -> B,
_ ifAbsent : () -> B) -> B {
switch(self) {
case is Some<A>: return ifPresent((self as! Some<A>).a)
case is None<A>: return ifAbsent()
default: fatalError("Maybe cannot have other subclasses")
}
}
}
class Some<V> : Maybe<V> {
let a : V
init(_ a : V) {
self.a = a
}
}
class None<V> : Maybe<V> {}
// The compiler is not able to distinguish the letters used for the associated types and
// the generics in Maybe; therefore, we need to use different letters in the definition of
// Maybe (or other data types). The extension does not allow us to write Maybe<V>, only Maybe,
// but it remembers the letter (V) that we used in the declaration of the class. If we use
// Maybe<A> in the declaration, the A in Functor overrides the A in Maybe and it just does
// not compile.
extension Maybe : Functor {
typealias F = ForMaybe
typealias A = V
func map<B>(_ f: @escaping (V) -> B) -> HK<ForMaybe, B> {
return self.fold(f >>> Maybe<B>.some, Maybe<B>.none)
}
}
extension Maybe : CustomStringConvertible {
var description: String {
return self.fold({ a in "Some(\(a))" }, { "None" })
}
}
class ForEither {}
typealias EitherOf<A, B> = HK2<ForEither, A, B>
typealias EitherPartial<A> = HK<ForEither, A>
class Either<L, R> : EitherOf<L, R> {
static func left(_ a : L) -> Either<L, R> {
return Left(a)
}
static func right(_ b : R) -> Either<L, R> {
return Right(b)
}
func fold<C>(_ ifLeft : (L) -> C,
_ ifRight : (R) -> C) -> C {
switch self {
case is Left<L, R>: return ifLeft((self as! Left<L, R>).a)
case is Right<L, R>: return ifRight((self as! Right<L, R>).b)
default: fatalError("Either cannot have more subclasses")
}
}
}
class Left<L, R> : Either<L, R> {
let a : L
init(_ a : L) {
self.a = a
}
}
class Right<L, R> : Either<L, R> {
let b : R
init(_ b : R) {
self.b = b
}
}
extension Either : Functor {
typealias F = EitherPartial<L>
typealias A = R
func map<C>(_ f : @escaping (R) -> C) -> HK<EitherPartial<L>, C> {
return self.fold(Either<L, C>.left,
f >>> Either<L, C>.right)
}
}
extension Either : CustomStringConvertible {
var description: String {
return self.fold({ a in "Left(\(a))"}, { b in "Right(\(b))" })
}
}
// The syntax to use a functor here is a bit cumbersome, but definitely works.
func plusFive<K, F>(_ input : K) -> HK<F, Int> where K : Functor, K.F == F, K.A == Int {
return input.map({ a in a + 5 })
}
let maybe = Maybe<Int>.some(4)
let none = Maybe<Int>.none()
let either = Either<String, Int>.right(5)
let left = Either<String, Int>.left("Nope")
print(plusFive(maybe))
print(plusFive(none))
print(plusFive(either))
print(plusFive(left))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment