Skip to content

Instantly share code, notes, and snippets.

@elm4ward
Last active February 3, 2018 23:03
Show Gist options
  • Save elm4ward/3c03db368cb2e74677a76e1693cddec4 to your computer and use it in GitHub Desktop.
Save elm4ward/3c03db368cb2e74677a76e1693cddec4 to your computer and use it in GitHub Desktop.
// ----------------------------------------------------------------------------------------------------
// ArrayZipper Comonads in Swift
//
// http://elm4ward.github.io/swift/functional/comonad/2016/03/30/monads-comonads.html
//
// 1. Array extensions
// 2. ArrayZipperError
// 3. ArrayZipper
// 4. ArrayZipper extensions
// 5. custom operators
// 6. extend methods
// 7. finally, the example
// ----------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------
// MARK: - 1. Array Extension
//
// Some initial setup functions necessary
// ----------------------------------------------------------------------------------------------------
extension Array {
/// take the first i Elements
func take(i: Int) -> SubSequence {
let drop = count - i
guard drop >= 0 && drop < count else { return dropLast(0) }
return dropLast(drop)
}
/// take the last i Elements
func takeLast(i: Int) -> SubSequence {
let drop = count - i
guard drop >= 0 && drop < count else { return dropFirst(0) }
return dropFirst(drop)
}
}
// ----------------------------------------------------------------------------------------------------
// MARK: - 2. ArrayZipperError
//
// mostly used to avoid an optional 'from' method in 'ArrayZipper'
// ----------------------------------------------------------------------------------------------------
enum ArrayZipperError: ErrorType {
case emptyList
}
// ----------------------------------------------------------------------------------------------------
// MARK: - 3. ArrayZipper
//
// the Zipper.
// Could also be build be an Array and Int pointing to the Element.
// ----------------------------------------------------------------------------------------------------
enum ArrayZipper<T> {
typealias Structure = (head: [T], focus: T, tail: [T])
case zipper([T], T, [T])
/// ArrayZipper from a Array of S
static func from<S>(a: [S]) throws -> ArrayZipper<S> {
guard let focus = a.first else { throw ArrayZipperError.emptyList }
return .zipper([], focus, Array(a.dropFirst()))
}
/// Get the structured values
var structure: Structure {
switch self {
case let .zipper(h, f, t): return (head: h, focus: f, tail: t)
}
}
/// Get all as Array
var all: [T] {
switch self {
case let .zipper(h, f, t): return h + [f] + t
}
}
/// Get current position of zipper
var position: Int {
switch self {
case let .zipper(h, _, _): return h.count
}
}
/// Move the zipper to next position
func moveTo(position: Int) -> ArrayZipper<T>? {
let ts = all
let ft = Array(ts.dropFirst(position))
guard let f = ft.first else { return nil }
return .zipper(Array(ts.dropLast(ts.count - position)), f, Array(ft.dropFirst()))
}
/// Get the zipper on the left side
func left() -> ArrayZipper<T>? {
return moveTo(position.predecessor())
}
/// Get the zipper on the right side
func right() -> ArrayZipper<T>? {
return moveTo(position.successor())
}
}
// ----------------------------------------------------------------------------------------------------
// MARK: - 4. ArrayZipper 'Functor'
//
// yes, a zipper is a Functor and therefore is has to have a map method
// ----------------------------------------------------------------------------------------------------
extension ArrayZipper {
/// map each value in the structure of the zipper
func map<A>(f: T -> A) -> ArrayZipper<A> {
switch self {
case let .zipper(head, focus, tail):
return .zipper(head.lazy.map(f), f(focus), tail.lazy.map(f))
}
}
}
// ----------------------------------------------------------------------------------------------------
// MARK: - 4. ArrayZipper 'Comonad'
//
// of course, a zipper is a Comonad.
// ----------------------------------------------------------------------------------------------------
extension ArrayZipper {
/// get the focus of the current structure
func extract() -> T {
return structure.focus
}
/// duplicate the structure and move at each position
/// will build a nested ArrayZipper
func duplicate() -> ArrayZipper<ArrayZipper<T>> {
let zippers = (0..<all.count).flatMap(moveTo)
return try! ArrayZipper.from(zippers).moveTo(position)!
}
/// get the duplicated structure and map with extend function which will 'reduce' the
/// ArrayZipper<ArrayZipper<T>> to ArrayZipper<S>
func extend<S>(f: ArrayZipper<T> -> S) -> ArrayZipper<S> {
return duplicate().map(f)
}
}
// ----------------------------------------------------------------------------------------------------
// MARK: - 4. Comonad extend operator
//
// A custom operator to call extend on a comonad. inspired by the original haskell operator
// ----------------------------------------------------------------------------------------------------
infix operator ->> { precedence 140 associativity left }
/// easy: call the extend method
func ->> <A, B>(lhs: ArrayZipper<A>, rhs: ArrayZipper<A> -> B) -> ArrayZipper<B> {
return lhs.extend(rhs)
}
// ----------------------------------------------------------------------------------------------------
// MARK: - 5. CoKleisli operator
//
// I dont even wanna talk about that name.
// A custom operator to combine extends for a comonad. inspired by the original haskell operator
// ----------------------------------------------------------------------------------------------------
infix operator =>= { precedence 150 associativity left }
/// great: combine 2 extend methods for reuse
func =>= <A, B, C>(lhs: ArrayZipper<A> -> B, rhs: ArrayZipper<B> -> C) -> ArrayZipper<A> -> C {
return { $0.extend(lhs).extend(rhs).extract() }
}
// ----------------------------------------------------------------------------------------------------
// MARK: - 6. Extends Methods
//
// Lets setup a couple of functions which examine the zipper and give us a value
// ----------------------------------------------------------------------------------------------------
func average(a: [Int]) -> Int {
guard !a.isEmpty else { return 0 }
return a.reduce(0, combine: +) / a.count
}
/// gets the average of the elements in the radius of the focus
func avg(radius radius: Int) -> ArrayZipper<Int> -> Int {
return { z in
let structure = z.structure
let ax = structure.head.takeLast(radius) + [structure.focus] + structure.tail.take(radius)
return average(Array(ax))
}
}
enum Judge {
case Better, Worse
}
/// tells wether the focus is better or worse than its predecessor
func judge<C: Comparable>(a: ArrayZipper<C>) -> Judge {
let structure = a.structure
guard let last = structure.head.last else { return .Better }
return structure.focus >= last ? .Better : .Worse
}
// ----------------------------------------------------------------------------------------------------
// MARK: - 7. Example
//
// finally the use case
// ----------------------------------------------------------------------------------------------------
/// the list of values
let values = [100, 160, 120, 223, 221, 180, 200, 170, 140, 190, 180, 170, 123, 101, 80, 90, 140, 190, 222]
/// the zipper
let valuesZipper: ArrayZipper<Int> = try! .from(values)
// wow ... so easy getting the average of each element in combination with its 3 siblings before and after
let avgs = valuesZipper ->> avg(radius: 3)
/// combine into a extend method that judges the averages
let didAverageGetBetter = avg(radius: 3) =>= judge
// wow ... so easy to see wether the average did get better or worse
let judges = valuesZipper ->> didAverageGetBetter
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment