具有高阶功能的编程风格的练习

本周,该章名为“前进”。 样式的约束不是直接调用函数,而是将其作为参数传递给下一个函数,稍后再调用。

这是5在编程风格焦点series.Other职位练习交包括:

  1. 以编程风格介绍练习
  2. 以编程风格进行练习,将内容堆叠起来
  3. 编程风格的练习,Kwisatz Haderach风格
  4. 编程风格的练习,递归
  5. 以高阶函数进行编程风格的练习 (本文)
  6. 以编程风格进行练习
  7. 以编程风格进行练习,回到面向对象的编程
  8. 编程风格的练习:地图也是对象
  9. 编程风格的练习:事件驱动的编程
  10. 编程风格的练习和事件总线
  11. 反思编程风格的练习
  12. 面向方面的编程风格的练习
  13. 编程风格的练习:FP&I / O
  14. 关系数据库风格的练习
  15. 编程风格的练习:电子表格
  16. 并发编程风格的练习
  17. 编程风格的练习:在线程之间共享数据
  18. 使用Hazelcast以编程风格进行练习
  19. MapReduce样式的练习
  20. 编程风格的练习总结

高阶函数

我不确定高阶函数的概念是否起源于函数式编程。 但是,这个概念本身很容易掌握:高阶函数可以用作另一个函数的函数参数或返回值。 从版本8开始,它在Java中可用,但是从其开始就在Scala和Kotlin中可用。

在Java中,它们主要与流结合使用:

Stream.of("A","B","C","D","E")
        .map(newFunction<String,String>(){
            @Override
            publicStringapply(Strings){
                returns.toLowerCase();
            }
        });
    }

Stream.of("A","B","C","D","E")
      .map(s->s.toLowerCase());

Stream.of("A","B","C","D","E")
      .map(String::toLowerCase);

前面的三行都做的完全一样,因为lambda被映射到功能接口的匿名类。

在Kotlin中,这非常相似,但有2点:

  1. 没有Kotlin会写匿名类,正是因为自一开始就可以使用高阶函数
  2. 就像Groovy中一样,lambda的默认参数名称是it
Stream.of("A","B","C","D","E")
      .map{it.toLowerCase()}


Stream.of("A","B","C","D","E")
      .map(String::toLowerCase)

调用函数

一旦将一个函数传递给另一个函数,下一个逻辑步骤就是调用它。 在Java中,需要知道高阶函数的类型。 例如,要执行一个Function ,需要调用apply()并传递具有期望类型的参数。 要执行Predicate ,该函数称为test()等。

Function<String,String>toLowerCase=String::toLowerCase;
toLowerCase.apply("A");
Predicate<String>isLowerCase=s->s.equals(s.toLowerCase());
isLowerCase.test("A");

Kotlin更一致,因为在KCallable接口上定义了一个名为call()函数。

funisLowerCase(string:String)=string==string.toLowerCase()
valcallable:Boolean=::isLowerCase.call("A")

但是, call()接受任意数量的Any?类型的参数Any? 。 调用者可以传递正确的数字和参数类型。 以下编译,但在运行时将失败:

valcallable:KCallable<String>=::isLowerCase.call("A",2,Any())

为了从编译器中受益,可以使用KFunctionX类型,其中X是函数接受的参数数量。 该类型为invoke()函数提供了正确数量的参数及其类型。

valok:KFunction<String,Boolean>=::isLowerCase.invoke("A") (1)
valko:KFunction<String,Boolean>=::isLowerCase.invoke("A",2,Any()) (2)
  1. 编译
  2. 不编译

锦上添花, invoke()是一个运算符函数,该运算符什么都不是,因此以下语法也是有效的:

(::isLowerCase)("A")

应用理论

在本练习中,入口点函数调用一个函数,该函数调用一个函数,依次类推到7个嵌套级别。 如果将KCallable更改为KFunction ,则顶部函数具有以下非常“有趣的”签名:

funreadFile(
    filename:String,
    function:KFunction2<
            List<String>,
            KFunction2<
                    List<String>,
                    KFunction2<
                            List<String>,
                            KFunction2<
                                    List<String>,
                                    KFunction2<
                                            List<String>,
                                            KFunction2<
                                                    List<Pair<String,Int>>,
                                                    KFunction1<List<Pair<String,Int>>,Map<String,Int>>,
                                                    Map<String,Int>>,
                                            Map<String,Int>>,
                                    Map<String,Int>>,
                            Map<String,Int>>,
                    Map<String,Int>>,
            Map<String,Int>>
):Map<String,Int>

尽管我相信静态类型化有很多好处,但是这当然不会使代码更易于阅读。 为了解决这个问题,Kotlin提供了类型别名 。 以下代码段使用它们来改善情况:

typealiasWordFrequency=Pair<String,Int>
typealiasWordFrequencies=List<WordFrequency>
typealiasLines=List<String>
typealiasWords=List<String>
typealiasMapFunction=KFunction1<WordFrequencies,Map<String,Int>>
typealiasSortFunction=KFunction2<WordFrequencies,MapFunction,Map<String,Int>>
typealiasFrequencyFunction=KFunction2<Words,SortFunction,Map<String,Int>>
typealiasRemoveFunction=KFunction2<Lines,FrequencyFunction,Map<String,Int>>
typealiasScanFunction=KFunction2<Lines,RemoveFunction,Map<String,Int>>
typealiasNormalizeFunction=KFunction2<Lines,ScanFunction,Map<String,Int>>
typealiasReadFunction=KFunction2<Lines,NormalizeFunction,Map<String,Int>>

funreadFile(filename:String,function:ReadFunction)=function(read(filename),::normalize)

这是最终代码的摘录:

funrun(filename:String)=(::readFile)(filename,::filterChars) (1)

funreadFile(filename:String,function:ReadFunction)=function(read(filename),::normalize) (2)

funfilterChars(lines:List<String>,function:NormalizeFunction):Map<String,Int>{ (3)
    valpattern="\\W|_".toRegex()
    valfiltered=lines
        .map{it.replace(pattern," ")}
    returnfunction(filtered,::scan) (3)
}
  1. 结合使用函数引用和invoke()的快捷方式运算符。 第二个参数是下一个要调用的函数上的函数引用
  2. 第二个参数的类型使用别名,以使代码更易于阅读。 它调用传递的function参数的invoke()方法,并传递下一个函数作为参考进行调用
  3. 其余的遵循相同的逻辑:使用高阶函数作为最后一个参数,并调用invoke()的快捷方式版本以继续链

结论

至于介绍,虽然类型有助于降低某些错误的可能性,但它们也会降低可读性。 幸运的是,Kotlin类型别名对此很有帮助。

call()invoke()是使用反射调用函数时要考虑的两个选项。 他们每个人都有优点和缺点。

这篇文章的完整源代码可以在Github上找到。

翻译自: https://blog.frankel.ch/exercises-programming-style/5/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值