Created
April 22, 2018 08:48
-
-
Save truizlop/15f1b70cda369f454a7c073870a99152 to your computer and use it in GitHub Desktop.
Typeclasses exploration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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