当前位置: 首页 > 科技观察

不学点高级函数怎么假装开心!

时间:2023-03-14 14:52:03 科技观察

如果你是函数式编程的入门者,你一定听说过高阶函数。它在维基百科中的中文解释是这样的:在数学和计算机科学中,高阶函数是至少满足以下条件之一的函数:接受一个或多个函数作为输入和输出一个函数看起来像是一个输入参数在ObjC语言中或者返回值是块的块或者函数,在Swift语言中就是输入参数或者返回值是函数的函数。那么他们在实际的开发过程中分别扮演了怎样的角色呢?我们将从入参、返回值、综合使用三个部分来看这个问题:函数作为入参函数作为入参似乎无论在ObjC时代还是Swift时代都是司空见惯的事情。例如,AFNetworking使用两个输入块分别回调成功和失败。Swift中增加了一个尾部闭包语法(最后一个参数是一个函数,可以不写括号也可以直接跟在括号外的方法名后面),例如:[1,2,3].forEach{iteminprint(item)}我们可以把输入参数为函数的函数分为两类,escape函数输入参数和noescape函数输入参数。区别在于输入参数函数是在执行过程中调用还是在执行过程外调用。在执行过程外调用一般用于回调目的,例如:Alamofire.request("https://httpbin.org/get").responseJSON{responseinprint(response.request)//originalURLrequestprint(response.response)//HTTPURLresponseprint(response.data)//serverdataprint(response.result)//resultofresponseserializationifletJSON=response.result.value{print("JSON:\(JSON)")}}这个response的输入函数作为回调返回网络请求,执行responseJSON函数时不会被调用。另外,我们观察一下forEach的代码。可以推断,作为参数输入的函数在forEach的执行过程中肯定会用到。执行后就没用了。这个类型就是noescape函数。您应该熟悉回调的用法。下面介绍一下noescape入参的一些用法:1.免构造函数看过GoF设计模式的同学不知道还记得构造函数模式吗。Android中的构造函数模式类似于以下内容:newAlertDialog.Builder(this).setIcon(R.drawable.find_daycycle_icon).setTitle("Reminder").create().show();构造一个对象需要很多参数,其中很多参数都有默认值,而这些参数对应的属性在以后如果不想被修改,可以使用这种模式来直观细腻的展示构造过程.如果使用noescape输入函数,可以更简单地构造这种代码,只需要传入一个输入参数为builder的对象,如下://这里实现classSomeBuilder{varprop1:Intvarprop2:Boolvarprop3:Stringinit(){//defaultvalueprop1=0prop2=trueprop3="somestring"}}classSomeObj{privatevarprop1:Intprivatevarprop2:Boolprivatevarprop3:Stringinit(_builderBlock:(SomeBuilder)->Void){letsomeBuilder=SomeBuilder()builderBlock(someBuilder)//输入参数的无转义使用prop1=someBuilder.prop1prop2=someBuilder.prop2prop3=someBuilder.prop3}}//当使用letsomeOjb=SomeObj{builderinbuilder.prop1=15builder.prop2=falsebuilder.prop3="haha"}2.自动配对操作经常用到,我们开发在此过程中,你会遇到必须配对才能正常工作的API,例如打开和关闭文件、进入编辑模式和退出编辑模式等。虽然swift语言给了我们defer这样的语法糖来防止人们忘记配对操作,但是代码看起来还是不太顺眼funcupdateTableView1(){self.section:0)],with:.fade)self.tableView.deleteRows(at:[IndexPath(row:5,section:0)],with:.fade)self.tableView.endUpdates()//容易遗漏或上面发生异常}funcupdateTableView2(){self.tableView.beginUpdates()defer{self.tableView.endUpdates()}self.tableView.insertRows(at:[IndexPath(row:2,section:0)],with:。fade)self.tableView.deleteRows(at:[IndexPath(row:5,section:0)],with:.fade)}使用noescape作为参数,我们可以封装要操作的流程,让上层看起来更regular//ExtendUITableViewextensionUITableView{funcupdateCells(updateBlock:(UITableView)->Void){beginUpdates()defer{endUpdates()}updateBlock(self)}}funcupdateTableView(){//使用self.tableView.updateCells时{(tableView)intableView.insertRows(at:[IndexPath(row:2,section:0)],with:.fade)tableView.deleteRows(at:[IndexPath(row:5,section:0)],with:.fade)}}function这里简单介绍一下作为入参。让我们看一下作为返回值的函数。函数作为返回值在你的日常开发中,肯定很少有使用函数作为返回值的情况。但是,如果能够简单地使用它,它会让代码一下子变得更加简洁。首先,没有争议的是,我们有很多API都需要函数作为入参,无论是上一节提到的escaping入参,还是noescape入参。所以很多时候,你写的代码会有很高的重复率,比如:letarray=[1,3,55,47,92,77,801]letarray1=array.filter{$0>3*3}letarray2=array.filter{$0>4*4}letarray3=array.filter{$0>2*2}letarray4=array.filter{$0>5*5}一段代码,从数组中求某个数的平方,如果是没有封装,看起来应该是这样的。为了简单起见,通常封装成如下两种形式:value:3)letarray2=biggerThanPowWith(array:array,value:4)letarray3=biggerThanPowWith(array:array,value:2)letarray4=biggerThanPowWith(array:array,value:5)如果使用higher的返回值函数-order函数,可以做成这样一个高级函数://一个函数funcbiggerThanPow2With(value:Int)->(Int)->Bool{return{$0>value*value}}letarray1=array.filter那个返回(Int)->Bool(biggerThanPow2With(value:3))letarray2=array.filter(biggerThanPow2With(value:4))letarray3=array.filter(biggerThanPow2With(value:2))letarray4=array.filter(biggerThanPow2With(value:5))你会话虽如此,两者之间没有区别。所以这里需要说一下使用高阶返回函数的一些好处1.不需要包装函数,也不需要打开原来的类。和上面那个简单的包一样,其实就是一个以数组为入参的包装函数。这样写代码和看代码的时候有点不适应。毕竟,每个人都喜欢OOP。如果要OOP,就要扩展原来的类。一种方法是添加扩展,或者直接向类中添加新方法。2.阅读代码时,一目了然。在使用简单包的时候,看代码的人并不知道内部使用了过滤函数。你必须查看源代码才能知道。但是在使用高阶函数的时候,马上就知道用到了系统库的过滤器。3.更容易复用这也是最关键的一点。细粒度的高阶函数可以更方便地复用。比如我们知道Set还有一个filter方法,复用是这样的:letset=Set(arrayLiteral:1,3,7,9,17,55,47,92,77,801)letset1=set.filter(biggerThanPow2With(value:3))letset2=set.filter(biggerThanPow2With(value:9))回忆一下上面的简单封装是不是导致无法复用了?返回函数的类似高阶函数的例子还有很多,比如上面提到的builder。如果每次都需要自定义,只是某个字段不一样,可以用高阶函数轻松构建:funcbuilderWithDifferentProp3(prop3:String)->(SomeBuilder)->Void{return{builderinbuilder.prop1=15builder.prop2=truebuilder.prop3=prop3}}letsomeObj1=SomeObj.init(builderWithDifferentProp3(prop3:"a"))letsomeObj2=SomeObj.init(builderWithDifferentProp3(prop3:"b"))letsomeObj3=SomeObj.init(builderWithDifferentProp3(prop3:"c"))引入参与返回值后,还有其他的A组合方式,即入参是函数,返回值也是函数。我们来看看这个情况。输入函数&&返回值函数这样的函数看起来很吓人,swift会将其声明为:funcsomeFunc(_a:(A)->B)->(C)->Dobjective-cwill被声明为-(id(^)(id))someFunc:(id(^)(id))block让我们从一个小例子开始,回想一下我们刚刚制作的biggerThanPow2With函数,如果我们想要一个notBiggerThanPow2With怎么办?你知道我不会说再写一个。所以我告诉你我会写:funcnot(_origin_func:@escaping(T)->Bool)->(T)->Bool{return{!origin_func($0)}}letarray5=array.filter(not(biggerThanPow2With(value:9)))不需要notBiggerThanPow2With函数,我们只需要实现一个not即可。它的输入参数是(T)->Bool,返回值也是(T)->Bool。只需要在执行块内部时使用否定即可。这样不仅可以解决刚才的问题,还可以解决任意(T)->Bool类型函数的求逆问题。例如,如果我们有一个odd(_:int)方法来过滤奇数,那么我们可以使用even=not(odd)来得到一个过滤偶数的函数。funcodd(_value:Int)->Bool{returnvalue%2==1}letarray6=array.filter(odd)letarray7=array.filter(not(odd))leteven=not(odd)letarray8=array.filter(even)你可以看看我们在上面biggerThanPow2With中讨论的内容。如果biggerThanPow2With不是返回函数的高阶函数,那么用not函数就不好处理了。综上所述,如果一个输入参数和返回值都是函数的函数是这样一个转换函数,它可以让我们用更少的代码组合更多的函数。另外需要注意的是,如果返回的函数是用入参的函数关闭的,那么这个入参函数就是对入参的转义。再给大家看两个函数,一个是交换参数的函数exchangeParam,另一个是柯里化函数currying:funcexchangeParam(_block:@escaping(A,B)->C)->(B,A)->C{return{block($1,$0)}}funccurring(_block:@escaping(A,B)->C,_value:A)->(B)->C{return{block(value,$0)}}在swift语言中>是一个参数为(a,b)的函数,所以>(5,3)==true。我们用exchangeParam来交换参数,变成了(b,a),那么exchangeParam(>)(5,3)等于false。currying函数将参数b固定为常数9,所以currying(exchangeParam(>),9)表示大于9的函数。本例中使用了所有的预制函数和通用函数,过滤子函数的要求数组中大于9的数组是在没有任何命令和业务函数声明的情况下实现的。试想一下,如果我们更多地使用这样的高级函数,代码中的很多逻辑是否可以更容易地相互组合,这就是函数式编程的魅力所在。综上所述,高阶函数的引入,无论是从函数式编程还是非函数式编程,都为我们带来了一定程度的代码简化,让我们的API更易用,复用性更充分。然而,本文中的示例只是冰山一角。更多的内容还是需要大家不断的尝试和创新。你也可以通过学习更多的函数式编程范式来加深理解。关于作者: