Swift 如何使嵌套平面图和地图更易于理解

Swift 如何使嵌套平面图和地图更易于理解,swift,functional-programming,monads,for-comprehension,Swift,Functional Programming,Monads,For Comprehension,假设我们有一个结构M: public struct M<T> { let value: T public init(_ value: T) { self.value = value } public func map<U>(f: T -> U) -> M<U> { return M<U>(f(value)) } public func flatMap<U>(f: T -> M<U

假设我们有一个结构
M

public struct M<T> {
    let value: T
    public init(_ value: T) { self.value = value }

    public func map<U>(f: T -> U) -> M<U> { return M<U>(f(value)) }
    public func flatMap<U>(f: T -> M<U>) -> M<U> { return f(value) }
}
func task1() -> M<Int> {
    return M(1)
}

func task2(value: Int = 2) -> M<Int> {
    return M(value)
}

func task3(value: Int = 3) -> M<Int> {
    return M(value)
}

func task4(arg1: Int, arg2: Int, arg3: Int) -> M<Int> {
    return M(arg1 + arg2 + arg2)
}
现在,假设我们要计算task1、task2和task3的值,然后将所有三个计算值作为参数传递给task4。这似乎需要使用对
flatMap
map
的嵌套调用:

let f1 = task1()
let f2 = task2()
let f3 = task3()

f1.flatMap { arg1 in
    return f2.flatMap { arg2 in
        return f3.flatMap { arg3 in
            return task4(arg1, arg2:arg2, arg3:arg3).map { value in
                print("Result: \(value)")
            }
        }
    }
}

但这看起来不太容易理解。有什么办法可以改进吗?例如,使用自定义运算符

好吧,为了便于参考,最好在这里记录Haskell在这种情况下所做的工作:

example1 = do
  arg1 <- task1
  arg2 <- task2
  arg3 <- task3
  value <- task4 arg1 arg2 arg3
  putStrLn ("Result: " ++ show value)
是的,你在这里所做的是重新发现Haskell的
do
-符号的动机它正是一种用于编写嵌套平面图的特殊平面语法

但这里有另一个可能与这个例子相关的技巧。请注意,在计算中,
task1
task2
task3
没有任何相互依赖关系。这可以作为设计“平面”实用程序结构的基础,用于将它们合并到一个任务中。在Haskell中,您可以通过
Applicative
类和模式匹配轻松实现这一点:

import Control.Applicative (liftA3, (<$>), (<*>))

-- `liftA3` is the generic "three-argument map" function, 
-- from `Control.Applicative`.
example3 = do
  -- `liftA3 (,,)` is a task that puts the results of its subtasks
  -- into a triple.  We then flatMap over this task and pattern match
  -- on its result. 
  (arg1, arg2, arg3) <- liftA3 (,,) task1 task2 task3
  value <- task4 arg1 arg2 arg3
  putStrLn ("Result: " ++ show value)

-- Same thing, but with `<$>` and `<*>` instead of `liftA3`
example4 = do
  (arg1, arg2, arg3) <- (,,) <$> task1 <*> task2 <*> task3
  value <- task4 arg1 arg2 arg3
  putStrLn ("Result: " ++ show value)
因此,一个想法是构建一个提供类似功能的实用程序库。一些示例操作:

  • 将异构类型的任务组合成复合任务。签名看起来像
    (M,…,M)->M
  • 多任务映射:将n个位置的函数映射到生成适当类型的n个任务上
  • 将一系列任务转换为产生一系列结果的任务

  • 请注意#1和#2具有等效功率。还要注意的是,如果我们谈论异步任务,这些操作比平面图有一个优势,那就是它们更容易并行化。

    如果
    task1
    task2
    task3
    彼此不依赖,那么您可以使用
    ap
    来自
    Applicative Functor
    的变体。然后您将一次获得所有值。但是,是的,像scala的理解的东西将是很好的。
    import Control.Applicative (liftA3, (<$>), (<*>))
    
    -- `liftA3` is the generic "three-argument map" function, 
    -- from `Control.Applicative`.
    example3 = do
      -- `liftA3 (,,)` is a task that puts the results of its subtasks
      -- into a triple.  We then flatMap over this task and pattern match
      -- on its result. 
      (arg1, arg2, arg3) <- liftA3 (,,) task1 task2 task3
      value <- task4 arg1 arg2 arg3
      putStrLn ("Result: " ++ show value)
    
    -- Same thing, but with `<$>` and `<*>` instead of `liftA3`
    example4 = do
      (arg1, arg2, arg3) <- (,,) <$> task1 <*> task2 <*> task3
      value <- task4 arg1 arg2 arg3
      putStrLn ("Result: " ++ show value)
    
    import Data.Traversable (sequenceA)
    
    example5 = do
      -- In this use, sequenceA turns a list of tasks into a
      -- task that produces a list of the originals results.
      [arg1, arg2, arg3] <- sequenceA [task1, task2, task3]
      value <- task4 arg1 arg2 arg3
      putStrLn ("Result: " ++ show value)