// This mimics the Bow library where they emulate HKTs with `Kind`
class Kind<F, A> {}
protocol Mappable {
static func map<A, B>(
_ fa: Kind<Self, A>,
_ f: @escaping (A) -> B
) -> Kind<Self, B>
}
final class ForArray: Mappable {
static func map<A, B>(
_ fa: Kind<ForArray, A>,
_ f: @escaping (A) -> B
) -> Kind<ForArray, B> {
ArrayK(fa.fix().value.map(f))
}
}
final class ArrayK<A>: Kind<ForArray, A> {
let value: [A]
init(_ value: [A]) { self.value = value }
}
extension Kind where F == ForArray {
// CAN FAIL, NOT ACTUALLY SAFE
func fix() -> ArrayK<A> { self as! ArrayK<A> }
}
final class ForOptional: Mappable {
static func map<A, B>(
_ fa: Kind<ForOptional, A>,
_ f: @escaping (A) -> B
) -> Kind<ForOptional, B> {
OptionalK(fa.fix().value.map(f))
}
}
final class OptionalK<A>: Kind<ForOptional, A> {
let value: A?
init(_ value: A?) { self.value = value }
}
extension Kind where F == ForOptional {
// CAN FAIL, NOT ACTUALLY SAFE
func fix() -> OptionalK<A> { self as! OptionalK<A> }
}
final class ForTask: Mappable {
static func map<A, B>(
_ fa: Kind<ForTask, A>,
_ f: @escaping (A) -> B
) -> Kind<ForTask, B> {
TaskK(Task<B, Error> {
let a = try await fa.fix().value.value
return f(a)
})
}
}
final class TaskK<A>: Kind<ForTask, A> {
let value: Task<A, Error>
init(_ value: Task<A, Error>) { self.value = value }
}
extension Kind where F == ForTask {
// CAN FAIL, NOT ACTUALLY SAFE
func fix() -> TaskK<A> { self as! TaskK<A> }
}
final class ForResult: Mappable {
static func map<A, B>(
_ fa: Kind<ForResult, A>,
_ f: @escaping (A) -> B
) -> Kind<ForResult, B> {
ResultK(fa.fix().value.map(f))
}
}
final class ResultK<A>: Kind<ForResult, A> {
let value: Result<A, Error>
init(_ value: Result<A, Error>) { self.value = value }
}
extension Kind where F == ForResult {
// CAN FAIL, NOT ACTUALLY SAFE
func fix() -> ResultK<A> { self as! ResultK<A> }
}
func add4Inside<F: Mappable>(_ fa: Kind<F, Int>) -> Kind<F, Int> {
F.map(fa) { $0 + 4 }
}
add4Inside(ArrayK([1,2,3,4])).fix().value //> [5,6,7,8]
let opt: Int? = 4
add4Inside(OptionalK(opt)).fix().value //> Optional(8)
Explanation:
// The pattern here is a lot more verbose, and less safe, than GAT and HKTs.
// While in the HKT to implement Mappable just required:
extension Array: Mappable<Array> {
static func map<A, B>(_ fa: Array<A>, _ f: (A) -> B) -> Array<B> {
fa.map(f)
}
}
// this instead requires:
final class ForArray: Mappable {
static func map<A, B>(
_ fa: Kind<ForArray, A>,
_ f: @escaping (A) -> B
) -> Kind<ForArray, B> {
ArrayK(fa.fix().value.map(f))
}
}
final class ArrayK<A>: Kind<ForArray, A> {
let value: [A]
init(_ value: [A]) { self.value = value }
}
extension Kind where F == ForArray {
func fix() -> ArrayK<A> { self as! ArrayK<A> }
}
// More important than all the extra verbosity, `fix` is actually unsafe.
// We could use it on an ArrayK and it would be fine,
// but we could just as easily implement some other Kind<ForArray, A>` class.
// Then fix() would fail on that new object.