PaversFRP

这是一个 Swift 框架,用于支持我的许多其他框架中的函数式编程。将这些功能集中到一个框架中,将有助于在其他框架或应用程序中使用函数式编程。

一些代码来自 Github 上的其他仓库,我将提供指向该仓库的链接。

大多数想法都可以在 Haskell 或其他函数式编程语言中找到对应物。要开始介绍这个框架,我建议用户先阅读我的文章Swift 中的类型论

类型特性

有一些不同类型的类型,它们在 Swift 中有对应的类型。

Sum Type(和类型),在 Swift 中对应的类型是 enum(枚举)。

Product Type(积类型),在 Swift 中对应的类型是 tuple(元组)和 struct(结构体)。

Exponential Type(指数类型),在 Swift 中对应的类型是 function(函数)和 closure(闭包)。

Existential Type(存在类型),在 Swift 中对应的类型是 protocol(协议)。

Recursive type(递归类型),在 Swift 中对应的类型是 indirect enum(间接枚举),它在某些情况下引用其 Self

不同类型的类型具有不同的属性,以方便解决一些问题。该框架的功能将大量利用这些概念。

惰性求值

函数式编程的另一个重要特性是惰性求值。惰性求值意味着延迟计算,直到需要其值时才进行计算。而 Swift 是一种急切求值的语言,也就是说,当你将一个表达式传递给一个函数调用时,该表达式会首先被求值,然后将结果值作为参数传递给该函数。因此,要在 Swift 中实现惰性求值,我们需要使用 function(函数)或 closure(闭包),(exponential type,指数类型),来包装表达式。换句话说,告诉我需要时如何生成该值,而不是直接给我确切的值。

以下是一个利用 Swift 中惰性求值的示例。

在 Haskell 中,斐波那契数列的计算方式如下:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

因为 Haskell 是一种惰性语言,它只在需要时部分地评估表达式。(评估到弱首范式

Haskell 使该表达式高效的另一个特性是函数值缓存,也就是说,如果使用相同的参数调用相同的函数,它将在第一次调用后返回缓存的值。

因此,在 Swift 中,我们可以将此斐波那契表达式实现如下。请注意,此实现没有缓存功能。

public enum List<A> {
  case Nil
  indirect case Cons(A, () -> List<A>)
}

public func zipList<A, B, C>(xs: @escaping () -> List<A>,
                             ys: @escaping () -> List<B>,
                             f: @escaping (A, B) -> C) -> List<C> {
  switch (xs(), ys()) {
  case (.Cons(let x, let xrest), .Cons(let y, let yrest)):
    return .Cons( f(x, y), {zipList(xs: xrest, ys: yrest, f: f)})
  default:
    return .Nil
  }
}

public func tail<A> (xs: @escaping () -> List<A>) -> () -> List<A> {
  switch xs() {
  case .Cons(_, let rest): return rest
  default: fatalError("List is empty")
  }
}

public func fib() -> List<Int> {
  return List<Int>.Cons(1, {List<Int>.Cons(1, {zipList(xs: fib, ys: tail(xs: fib), f: +)})})
}

组合

组合是可重用性的关键。因为在 Haskell 中,一切都是纯函数,没有副作用,每个输出值只取决于输入值。因此,可以将一些表达式或函数组合起来,形成更大的表达式或函数。所以 Haskell 中有一种说法,一个程序是一个解决特定问题的大型表达式。

因此,我们有基本函数组合。

public func >>> <A, B, C> (f: @escaping (A) -> B, g: @escaping (B) -> C) -> (A) -> C {
  return { g(f($0)) }
}

此外,我们还有更多不同类型的组合。

例如,与 Monad 相关的 Kleisli 组合。

以下函数用于从左到右组合两个产生可选值的函数。

public func >-> <T, U, V>(f: @escaping (T) -> U?, g: @escaping (U) -> V?) -> (T) -> V? {
  return { x in f(x) >>- g }
}

柯里化

在 Haskell 中,每个函数默认都是柯里化的。这将有助于函数组合。

Swift 中的柯里化看起来像这样:

public func curry<A, B, C>(_ function: @escaping (A, B) -> C) -> (A) -> (B) -> C {
    return { (a: A) -> (B) -> C in { (b: B) -> C in function(a, b) } }
}

Swift 类型类别中的初始对象和终结对象。

初始对象是一种类型,对于 Swift 中的每种类型,都存在一个从初始对象到该类型的唯一函数。

在 Swift 中,初始对象是 Never,及其同构类型,例如无 case 的枚举。

终结对象是一种类型,对于 Swift 中的每种类型,都存在一个从该类型到终结对象的唯一函数。

在 Swift 中,终结对象是 Void,及其同构类型,例如 (),struct 包裹的 ()

在此框架中,终结对象的一个用途是在不需要值时忽略该值。

public func terminal<A>(_ x: A) -> () {
  return ()
}

Swift 中的数学概念

使用数学概念来捕获类型的常见行为模式,可以使该模式更加明确,并更方便地重用该模式。

半群 (Semigroup)

/// A type is a Semigroup if it has an associative, binary operation.
public protocol Semigroup {
  /// An associative operation, i.e. a.op(b.op(c)) == (a.op(b)).op(c)
  func op(_ other: Self) -> Self
}

幺半群 (Monoid)

/// A type is `Monoid` if it is `Semigroup` with an identity that
/// combine this identity with other value of this type would
/// result to that value.
public protocol Monoid: Semigroup {
  static func identity () -> Self
}

函子 (Functor)

Funcotr 类型表现为封装值的原始上下文。因此,它具有 fmap 函数,可以更改该上下文中存在的值。

数组作为函子 (Functor)

public func <^> <T, U>(f: (T) -> U, a: [T]) -> [U] {
  return a.map(f)
}

Optional 作为函子 (Functor)

public func <^> <T, U>(f: (T) -> U, a: T?) -> U? {
  return a.map(f)
}

Applicative

Applicative 类型是一个 Funcotr,同时,当一个函数存在于该 applicative 上下文中时,该函数可以应用于与该上下文中存在的值。 此外,将存在一个函数,用于将任何值提升到 applicative 上下文中。

public func <*> <T, U>(fs: [(T) -> U], a: [T]) -> [U] {
  return a.apply(fs)
}
public func pure<T>(_ a: T) -> [T] {
  return [a]
}

Optional 作为 Applicative

public func <*> <T, U>(f: ((T) -> U)?, a: T?) -> U? {
  return a.apply(f)
}
public func pure<T>(_ a: T) -> T? {
  return .some(a)
}

Monad

Monad 类型是一个 Applicative,同时具有 bind 函数来扩展其上下文能力,从而使以下函数对上下文敏感。

数组作为 Monad

public func >>- <T, U>(a: [T], f: (T) -> [U]) -> [U] {
  return a.flatMap(f)
}

Optional 作为 Monad

public func >>- <T, U>(a: T?, f: (T) -> U?) -> U? {
  return a.flatMap(f)
}

高阶类型 (Higher Kinded Type)(仅用于实验)

通过使用 Swift 类型系统来抽象 Functor、Applicative 和 Monad(以 Protocol 的形式)来模拟 Swift 中的高阶类型,以便让这些抽象的类型符合响应的协议。

该模拟机制是使用一个结构来保存上下文包装类型的类型信息,当需要解包包装类型的值时,使用该信息来获取特定类型的值,而不是 Any。

/// * -> *
/// tell what the type is in the HKTValueKeeper
public struct HKT_TypeParameter_Binder <HKTValueKeeper, HKTArgumentType> {
  let valueKeeper: HKTValueKeeper
}
public typealias HKT<F, A> = HKT_TypeParameter_Binder <F, A>

/// A protocol all type constructors must conform to.
/// * -> *
public protocol HKTConstructor {
  /// The existential type that erases `Argument`.
  /// This should only be initializable with values of types created by the current constructor.
  associatedtype HKTValueKeeper
  /// The argument that is currently applied to the type constructor in `Self`.
  associatedtype A
  var typeBinder: HKT_TypeParameter_Binder<HKTValueKeeper, A> { get }
  static func putIntoBinder(with value: Self) -> HKT_TypeParameter_Binder<HKTValueKeeper, A>
  static func extractValue(from binder: HKT_TypeParameter_Binder<HKTValueKeeper, A>) -> Self
}

extension HKTConstructor {
  public var typeBinder: HKT_TypeParameter_Binder<HKTValueKeeper, A> {
    return Self.putIntoBinder(with: self)
  }
}

基于上述机制,我们可以在 Protocol 中抽象 functor、applicative 和 monad 概念。

/// fmap :: (a -> b) -> f a -> f b
public protocol Functor: HKTConstructor {
  typealias F = HKTValueKeeper
  static func fmap<B>(f: (A) -> B, fa: HKT<F, A>) -> HKT<F, B>
}


/// pure :: a -> f a
/// apply :: f (a -> b) -> f a -> f b
public protocol Applicative: Functor {
  static func pure(a: A) -> HKT<F, A>
  static func apply<B>(f: HKT<F, (A) -> B>, fa: HKT<F, A>) -> HKT<F, B>
}


/// return :: a -> m a
/// bind :: m a -> (a -> m b) -> m b
public protocol Monad: Applicative {
  typealias M = HKTValueKeeper
  static var `return`: (A) -> HKT<M, A> {get}
  static func bind<B> (ma: HKT<M, A>, f: (A) -> HKT<M, B>) -> HKT<M, B>
}

extension Monad {
  public static var `return`: (A) -> HKT<M, A> {return pure}
}

然后我们有一个数组符合这些协议的例子如下:

public struct ArrayValueKeeper {
  public let value: Any
  init<T>(_ array: [T]) { self.value = array}
}

extension Array: HKTConstructor {
  
  public typealias HKTValueKeeper = ArrayValueKeeper
  
  public static func putIntoBinder(with value: Array<Element>) -> HKT_TypeParameter_Binder<HKTValueKeeper, Element> {
    return HKT_TypeParameter_Binder<HKTValueKeeper, Element>(valueKeeper: HKTValueKeeper(value))
  }
  
  public static func extractValue(from typeBinder: HKT_TypeParameter_Binder<HKTValueKeeper, Element>) -> Array {
    return typeBinder.valueKeeper.value as! Array<Element>
  }
}

extension Array: Functor {
  public static func fmap<B>(f: (Element) -> B, fa: HKT_TypeParameter_Binder<HKTValueKeeper, Element>) -> HKT_TypeParameter_Binder<HKTValueKeeper, B> {
    return extractValue(from:fa).map(f).typeBinder
  }
}

extension Array: Applicative {
  public static func pure(a: Element) -> HKT_TypeParameter_Binder<HKTValueKeeper, Element> {
    return [a].typeBinder
  }
  
  public static func apply<B>(f: HKT_TypeParameter_Binder<ArrayValueKeeper, (Element) -> B>, fa: HKT_TypeParameter_Binder<ArrayValueKeeper, Element>)
    -> HKT_TypeParameter_Binder<ArrayValueKeeper, B> {
      let fs = Array<(Element) -> B>.extractValue(from: f)
      let fas = Array<Element>.extractValue(from: fa)
      let fbs = fs.flatMap{ fas.map($0) }
      return fbs.typeBinder
  }
}

extension Array: Monad {
  public static func bind<B>
    (ma: HKT_TypeParameter_Binder<ArrayValueKeeper, Element>, f: (Element) -> HKT_TypeParameter_Binder<ArrayValueKeeper, B>)
    -> HKT_TypeParameter_Binder<ArrayValueKeeper, B> {
      return extractValue(from: ma).map(f).flatMap{Array<B>.extractValue(from: $0)}.typeBinder
  }
}

注意

此框架中还有更多内容,此框架的每个 API 的文档将保留在源文件中。

如何使用

Swift Package Manager(Swift 包管理器)

将以下包依赖项添加到您的项目中。

.package(url: "https://github.com/KeithPiTsui/PaversFRP.git", from: "1.0.0"),

手动

  1. 将 ./Source/PaversFRP 文件夹及其内容复制到您的项目中,并使用源文件。
  2. 或者克隆此 repo,然后使用 Swift 包管理器生成其 Xcode 项目文件,并将该 Xcode 项目导入到您自己的项目中。

部分代码的来源

Operators 很大程度上借鉴自 Rune

基本想法和代码很大程度上借鉴自 Kickstarter-Prelude