Swift柯里化

Posted WoodBear009

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift柯里化相关的知识,希望对你有一定的参考价值。

Curry

  今天同事推荐了一个swift柯里化相关的库,点击打开链接,打开看了看具体实现,瞬间不明觉厉,于是想好好研究研究他是怎么写的,顺便加强一下对柯里化的理解

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


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)  


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


public func curry<A, B, C, D, E>(_ function: @escaping (A, B, C, D) -> E) -> (A) -> (B) -> (C) -> (D) -> E 
    return  (a: A) -> (B) -> (C) -> (D) -> E in  (b: B) -> (C) -> (D) -> E in  (c: C) -> (D) -> E in  (d: D) -> E in function(a, b, c, d)    


public func curry<A, B, C, D, E, F>(_ function: @escaping (A, B, C, D, E) -> F) -> (A) -> (B) -> (C) -> (D) -> (E) -> F 
    return  (a: A) -> (B) -> (C) -> (D) -> (E) -> F in  (b: B) -> (C) -> (D) -> (E) -> F in  (c: C) -> (D) -> (E) -> F in  (d: D) -> (E) -> F in  (e: E) -> F in function(a, b, c, d, e)     


public func curry<A, B, C, D, E, F, G>(_ function: @escaping (A, B, C, D, E, F) -> G) -> (A) -> (B) -> (C) -> (D) -> (E) -> (F) -> G 
    return  (a: A) -> (B) -> (C) -> (D) -> (E) -> (F) -> G in  (b: B) -> (C) -> (D) -> (E) -> (F) -> G in  (c: C) -> (D) -> (E) -> (F) -> G in  (d: D) -> (E) -> (F) -> G in  (e: E) -> (F) -> G in  (f: F) -> G in function(a, b, c, d, e, f)      


public func curry<A, B, C, D, E, F, G, H>(_ function: @escaping (A, B, C, D, E, F, G) -> H) -> (A) -> (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> H 
    return  (a: A) -> (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> H in  (b: B) -> (C) -> (D) -> (E) -> (F) -> (G) -> H in  (c: C) -> (D) -> (E) -> (F) -> (G) -> H in  (d: D) -> (E) -> (F) -> (G) -> H in  (e: E) -> (F) -> (G) -> H in  (f: F) -> (G) -> H in  (g: G) -> H in function(a, b, c, d, e, f, g)       


public func curry<A, B, C, D, E, F, G, H, I>(_ function: @escaping (A, B, C, D, E, F, G, H) -> I) -> (A) -> (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> I 
    return  (a: A) -> (B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> I in  (b: B) -> (C) -> (D) -> (E) -> (F) -> (G) -> (H) -> I in  (c: C) -> (D) -> (E) -> (F) -> (G) -> (H) -> I in  (d: D) -> (E) -> (F) -> (G) -> (H) -> I in  (e: E) -> (F) -> (G) -> (H) -> I in  (f: F) -> (G) -> (H) -> I in  (g: G) -> (H) -> I in  (h: H) -> I in function(a, b, c, d, e, f, g, h)        
   。。。。。。。。。。。。。。。。。。。。。。。。。。。。

Introduction to Function Currying in Swift

在它的readme里找到了这篇博客,看后豁然开朗,简单翻译一下(建议看原文)

原文:点击打开链接

  柯里化是指把一个多参数的函数变换成一个单参数的新函数,这个新函数接收余下的参数而且返回结果。经过不断的拆解,一个多参数的函数可以被转化成一系列单参数函数,具体的拆解粒度可以根据具体使用情况而定。

柯里化的应用

先来看看下面这个函数,两个整型参数a、b,然后把它们相加

func add(a: Int, b: Int) -> Int 
  return a + b
我们可以按如下方式调用:

let sum = add(2, 3) // sum = 5
但是设想下面这种情况,如果我们想对一列数字进行加2的处理,得到一个新的序列。借助Range,并对它进行map转换,如下

let xs = 1...100
let x = xs.map  add($0, 2)  // x = [3, 4, 5, 6, etc]
看起来不是很糟,但是每次都要传入一个默认参数2,总是会让人不爽。而且当我们的处理函数更加复杂时(有多个参数),我们可能就需要传入更多的默认参数。这种类似情况下,便可以借助柯里化帮助我们。

  回顾一下我们的add函数定义,传入两个int参数,并返回一个int结果。但是看看map函数的定义,形式如下:

extension Range<A> 
  func map<B>(transform: A -> B) -> [B]
上面这个扩展实际是无法编译的,这里仅仅是为了更清楚的说明问题

注:实际的系统定义是这样的

public func map<T>(_ transform: (Bound) throws -> T) rethrows -> [T]

可以看到,实际上map函数接收一个参数(transform,参数类型是一个函数,函数传入一个A,返回一个B),并返回一个值([B]).所以我们在调用时可以不使用闭包,而是直接传入一个函数。利用这个特点,我们可以给add函数定义一个包装函数,并直接传给map,如下:

func addTwo(a: Int) -> Int 
  return add(a, 2)


let xs = 1...100
let x = xs.map(addTwo) // x = [3, 4, 5, 6, etc]
看起来不错,不过这种实现太过有针对性了,如果我们有加3或加100的需求。按照上面这种方式,我们需要针对每个需求都定义一个相应的包装函数,更好的方式应该是做一个更为通用性的支持,比如让我们的函数返回一个函数。修改add的定义:

func add(a: Int) -> (Int -> Int) 
  return  b in a + b 
现在我们的函数参数由两个变成了一个。而它的返回值变成了一个函数,这个函数有一个参数,作用是实现a+b,a是add的参数,而b则是返回函数的参数。这种实现意味着:

1.如果我们想调用该函数,并提供所有参数,立刻得到结果,我们的调用形式变成了这样

let sum = add(2)(3) // sum = 5
2.这允许我们"分步"调用add函数.当我们传入第一个参数后,会得到一个新的函数,这个函数等待接收剩下的第二个参数,直到第二个参数被提供时,我们将得到最终的结果。

这样有什么好处吗?
它使我们可以将一个多参数的大函数拆解成一个一个的小函数,以减少每次调用时的参数要求。以我们的addTwo函数为例:

let addTwo = add(2)
addTwo是一个接收一个int参数,返回一个int值的函数变量(let), 我们可以直接把它传给map

let addTwo = add(2)
let xs = 1...100
let x = xs.map(addTwo) // x = [3, 4, 5, 6, etc]

(后面一段关于swift语法层面上对柯里化的支持就不翻译了,因为swift3已经取消了对柯里化的支持)

包装柯里化函数的套路

  如果我们给NSNumber定义一个叫做add的扩展方法,如下

extension NSNumber 
  class func add(a: NSNumber, b: NSNumber) -> NSNumber 
    return NSNumber(integer: a.integerValue + b.integerValue)
  
  现在,如果我们决定对这个方法进行柯里化

  首先,我们需要定义一个函数,参数是一个闭包,闭包的定义遵循NSNumber.add方法,即(NSNumber, NSNumber) -> NSNumber.如下

func curry(localAdd: (NSNumber, NSNumber) -> NSNumber) 
 然后为这个函数定义返回值,这个返回值是一个闭包,闭包接收一个NSNumber参数,再返回一个闭包,返回的闭包接收一个NSNumber,返回一个NSNumber,(NSNumber -> (NSNumber -> NSNumber))
func curry(localAdd: (NSNumber, NSNumber) -> NSNumber) -> (NSNumber -> (NSNumber -> NSNumber)) 
其实就是传入一个两个参数的函数localAdd,返回一个只有一个参数的函数,这个函数返回的函数比localAdd少了一个参数,这样就相当于剥离了一个参数。

最后是函数的具体实现

func curry(localAdd: (NSNumber, NSNumber) -> NSNumber) -> (NSNumber -> (NSNumber -> NSNumber)) 
  return  a: NSNumber in
             b: NSNumber in
              return localAdd(a, b) // returns an NSNumber
            
         
调用
let curriedAdd = curry(NSNumber.add)
let addTwo = curriedAdd(2)

let xs = 1...100
let x = xs.map(addTwo) // [3, 4, 5, 6, etc]

  利用swift的范型机制,我们可以使柯里化的包装更为通用,如下

func curry<A, B, C>(f: (A, B) -> C) -> (A -> (B -> C)) 
  return  a: A in
             b: B in
              return f(a, b) // returns C
            
         
套路其实都是一样的,再利用一些swift的语法糖精简一下代码,最终可以得到如下定义
func curry<A, B, C>(f: (A, B) -> C) -> A -> B -> C 
  return  a in  b in f(a, b)  
调用
let add = curry(+)

let xs = 1...100
let x = xs.map(add(2)) // [3, 4, 5, 6, etc]

以上是关于Swift柯里化的主要内容,如果未能解决你的问题,请参考以下文章

Swift柯里化

Swift柯里化

Swift函数柯里化(Currying)简谈

js - 理解函数柯里化

js - 理解函数柯里化

[转]js函数式变成之函数柯里化