List implementation in Swift

Functional data structure is slightly different from object oriented one. It have persistence and not mutated. I implement this data structure in Swift which have characteristic both object oriented and functional programming. The first example is list.

List declaration

List is declared using enum. nil means empty and cons means pair of value(type T) and list that concatenated to the end of itself. indirect is necessary to use itself recursive(cons(T, List<T>)).

enum List<T: Equatable> {
    case `nil`
    indirect case cons(T, List<T>)
}

List must be able to check the equivalence. We can enable it by conforming List to Equatable protocol.

extension List: Equatable {

    static func ==(lhs: List, rhs: List) -> Bool {
        switch (lhs, rhs) {
        case (.`nil`, .`nil`):
            return true
        case (.cons(let h, let t), .cons(let h1, let t1)):
            return h == h1 && t == t1
        default:
            return false
        }
    }
}

let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
let list1 = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
let list2 = List<Int>.cons(1, .cons(2, .cons(3, .cons(4, .`nil`))))
list == list1 // true
list == list2 // false

Conform to CustomStringConvertible protocol is useful to pretty print in the console.

extension List: CustomStringConvertible {

    var description: String {
        switch self {
        case .`nil`:
            return "nil"
        case .cons(let head, let tail):
            return "\(head),\(tail)"
        }
    }
}

let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
print(list) //1,2,3,nil

Variable

There is two point that pays to attention. First, All variable have the same format that use pattern match to extract associated values of cons. Second, length and last use recursion. Functional programming many time use these technics.

extension List {

    var isEmpty: Bool {
        switch self {
        case .`nil`:
            return true
        case .cons:
            return false
        }
    }

    var head: T? {
        switch self {
        case .`nil`:
            return nil
        case .cons(let head, _):
            return head
        }
    }

    var tail: List<T>? {
        switch self {
        case .`nil`:
            return nil
        case .cons(_, let tail):
            return tail
        }
    }

    var length: Int {
        switch self {
        case .`nil`:
            return 0
        case .cons(_, let tail):
            return 1 + tail.length
        }
    }

    var last: T? {
        switch self {
        case .`nil`:
            return nil
        case .cons(let head, .`nil`):
            return head
        case .cons(_, let tail):
            return tail.last
        }
    }

}

let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
list.isEmpty // false
list.head // 1
list.tail // 2,3,nil
list.length // 3
list.last // 3

Function

I implemented four basic functions. They used recursion and pattern match too. The Flow of their is close. Take add(value: T) as an example, it checks self and create new cons until it has head and tail. The tail of new cons call tail.add(value: value)and it repeat several time . Creating new cons each time called add(value: value) means create intermediate state as other instance. It able to persist the state of List. We can refer to initial state of List after called add(value: T).

extension List {

    func add(value: T) -> List<T> {
        switch self {
        case .`nil`:
            return List<T>.cons(value, .`nil`)
        case .cons(let head, let tail):
            return .cons(head, tail.add(value: value))
        }
    }

    func remove(index: Int) -> List<T> {
        switch (self, index) {
        case (.`nil`, _):
            return self
        case (.cons(_, let tail), 0):
            return tail
        case (.cons(let head, let tail), let index):
            return .cons(head, tail.remove(index: index - 1))
        }
    }

    func update(index: Int, value: T) -> List<T> {
        switch (self, index) {
        case (.`nil`, _):
            return self
        case (.cons(_, let tail), 0):
            return .cons(value, tail)
        case (.cons(let head, let tail), let index):
            return .cons(head, tail.update(index: index - 1, value: value))
        }
    }

    func append(list: List<T>) -> List<T> {
        switch self {
        case .`nil`:
            return list
        case .cons(let head, let tail):
            return List.cons(head, tail.append(list: list))
        }
    }

}

let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
list.add(value: 100) // 1,2,3,100,nil
list.remove(index: 1) // 1,3,nil
list.update(index: 0, value: 100) // 100,2,3,nil
list.append(list: list) // 1,2,3,1,2,3,nil

Higher order function

foldr and foldl is convolution function that scan value of List and make a computed result. map, filter is a function using foldr. Each value of List applied to function that passed by argument. The things that can be map over is called Functor. it is the abstraction of List<T> and Optional<T> and so on. Learning Functor might be good, if you are interested in. flatMap can keep the context of function and chain it.

extension List {

  func foldr<R>(acc: R, f: (T,R) -> R) -> R {
      switch self {
      case .`nil`:
          return acc
      case .cons(let head, let tail):
          return f(head, tail.foldr(acc: acc, f: f))
      }
  }

  func foldl<R>(acc: R, f: (R,T) -> R) -> R {
      switch self {
      case .`nil`:
          return acc
      case .cons(let head, let tail):
          return tail.foldl(acc: f(acc, head), f: f)
      }
  }

  func map<R>(f: @escaping (T) -> R) -> List<R> {
      return foldr(acc: List<R>.`nil`) { acc, list in
          return List<R>.cons(f(acc), list)
      }
  }

  func filter(f: @escaping (T) -> Bool) -> List<T> {
      return foldr(acc: List<T>.`nil`, f: { acc, list in
          if f(acc) {
              return List<T>.cons(acc, list)
          }
          return list
      })
  }

  func flatMap<R>(f: @escaping (T) -> List<R>) -> List<R> {
      return foldr(acc: List<R>.`nil`, f: { acc, list in
          return f(acc).append(list: list)
      })
  }

}

let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
list.map { $0 * 10 } // "10,20,30,nil
list.filter { $0 % 2 == 0 } // 2,nil
list.flatMap { .cons($0, .cons($0, .`nil`)) } // 1,1,2,2,3,3,nil
  • このエントリーをはてなブックマークに追加