美文网首页互联网科技程序员互联网科技
从五大语言看函数和lambda表达式

从五大语言看函数和lambda表达式

作者: Android开发技术分享 | 来源:发表于2019-03-06 21:24 被阅读6次

    前言

    最近Kotlin看得挺爽,曾经比较Java和JavaScript,
    遗憾过Java的函数太low,Kotlin在函数方面完全弥补了Java的缺憾。
    虽然java8支持了lambda表达式,但是还是没有kotlin爽。
    今天只谈函数和lambda,至于函数式编程,就不班门弄斧了。


    一、从Kotlin的函数说起

    在java中似乎并不怎么说函数,而是说方法,方法是对象的行为能力,那函数是什么?

    0.函数是什么?

    高中的数学是这样定义函数这个概念的:

    设A,B为非空的数集,如果按照某种确定的对应关系f,  
    使对于集合A中的任意的任意一个数x,在集合B中都有唯一确定的数f(x)和它对应,
    那么就称"f:A→B"为从集合A到集合B的一个函数,记作:
    y=f(x),x∈A
    
    其中,x叫做自变量,x的取值范围叫做函数的[定义域]
    与x的值对应的y值叫做函数值,函数值的集合{f(x)|x∈A}叫做函数的[值域]
    

    数学中一元函数的组成是两个集合和一个对应法则,
    每个自变量在对应法则的映射下都能获得唯一因变量。 我更愿意将数学中的函数看做对应法则下,自变量的所以变化集合
    这貌似和编程中的函数是两个概念,但是在思想上还是有相似之处的:

    如果将自变量看做输入状态,在对应法则之下,每个输入都对应着唯一对应的输出状态  
    而编程中的函数也是做类似的事:将输入的材料数据通过逻辑处理,形成特定输出,只是变化维度(参数)比较多
    复制代码
    

    1.Kotlin中函数的形式

    拿下面的函数来说,对于输入x总能保持唯一的y输出

    fun fx(x: Int): Int {
        val y = x + 2
        return y
    }
    

    -- 也许你会说:"这TM不就是加个2吗,需要讲的这么费劲?",
    -- 我想说:"不要太纠结表象,我写成val y = Math.sqrt(Math.exp(x) - 3 * Math.acos(x)) - Math.log(x)就会很高大上吗?"
    -- 在我眼中,这只是一种对应关系,它的本质和它的表示并没有关系,就算写成val y = 1,它的本质并不会改变:
    -- 仍是对于输入x总能保持唯一的y输出,这就是抽象,太在意表象就会肤浅以致视野的局限。


    2.Kotlin中函数的类型

    Kotlin中的函数也是一种数据类型,其类型为:(形参类型,形参类型)->返回值类型
    在Kotlin中使用::函数名获取一个函数的引用,函数是可以作为一个对象存在的

    val line: (Double) -> Double
    line = ::fx
    line(8.0)//10.0
    
    println(line)//fun fx(kotlin.Double): kotlin.Double
    println(line is (Double) -> Double)//true
    
    |-- 从效果上,普通视野来看就是让入参+2,并没有什么了不起的
    |-- 但从整个宏观来看该函数实现了一个 y = x + 2 的线性数据转换器,是不是高大上了一点
    

    3.函数的入参

    现在有一个gx,实现y=e^x的数据转化器。

    fun gx(x: Double): Double {
        val y = Math.exp(x)
        return y
    }
    

    你也许可以想到:既然函数可以作为对象,那么也可以当做入参
    然后就一不小心拼出了下面这个看起来挺帅气的函数,这时让fx作为入参
    脚指头想想应该也知道是y = e^(x+2),这就实现了两个函数的叠合。

    fun gx(x: Double, f: (Double) -> Double): Double {
        val y = Math.exp(f(x))
        return y
    }
    
    println(gx(0.0, ::fx))//7.38905609893065
    

    4.Lambda闪亮登场

    入参是函数,函数可以写成Lambda表达式,这里gx的函数入参类型:(Double) -> Double
    对应的Lambda表达式形式为:{ 参数名:Double -> 若干语句 最后一句返回Double},
    然后下面图形的数据转换器就ok了,将自变量x通过sin转换器后,再通过exp转化器,也可得到唯一的输出

    |-- 使用匿名函数,不用Lambda
    gx(5.0, fun(x: Double): Double {
        return Math.sin(x)
    })
    
    |-- 使用已存在的函数,不用Lambda
    gx(5.0, ::sin)
    
    |-- 使用Lambda,标准型--------------------
    gx(5.0, { x: Double ->
        Math.sin(x)
    })//0.3833049951722714
    
    |-- Lambda特性:作为最后一参可置后--------------
    gx(5.0) { x: Double ->
        Math.sin(x)
    }//0.3833049951722714
    
    |-- 可推导出变量类型,变量类型可省略------------------
    gx(5.0) { x ->
        Math.sin(x)
    }//0.3833049951722714
    
    |-- 只有一个参数时可以用it代替,省略变量---------------
    |-- 这样一看是不是对Kotlin的Lambda语法有了些认识
    gx(5.0) {Math.sin(it)}//0.3833049951722714
    

    好了,Lambda的引入完成,也许你有点晕,没关系,继续看


    二、从map函数看lambda表达式

    1.基上所有的语言都有map等操作符,拿Kotlin来看

    val ints = IntArray(10) { it }//初始化 0 1 2 3 4 5 6 7 8 9
    
    ints.map {
        it * it
    }.forEach { print("$it "); }//0 1 4 9 16 25 36 49 64 81
    

    2.Array的map函数源码分析

    ---->[_Arrays.kt#map]-----------------------
    public inline fun <R> IntArray.map(transform: (Int) -> R): List<R> {
        return mapTo(ArrayList<R>(size), transform)
    }
    |-- map函数的入参是 (Int) -> R 类型的函数,返回值是 List<R>
    |-- 它调用了mapTo方法
    
    ---->[_Arrays.kt#mapTo]-----------------------
    public inline fun <R, C : MutableCollection<in R>> IntArray.mapTo(destination: C, transform: (Int) -> R): C {
        for (item in this)
            destination.add(transform(item))
        return destination
    }
    |-- 这方法头有点长,仔细看看:方法入参 destination,类型C,其中C是MutableCollection类型的  
    |-- 从上面传入的ArrayList<R>(size)来看,是一个size尺寸的空列表,第二参仍是刚才的函数transform
    |-- 让this的所有元素经过transform方法,然后加入到空列表里,再将destination返回出去
    |-- 这样一看map方法也没有想象中的那么神奇,也可以看出map并不会污染原数组
    

    3.Java中的stream流中的map

    关于lambda表达式在Java中最常见的应数一个方法的接口,在stream流中便是家常便饭

    List<Integer> ints = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
    List<Integer> list = ints.stream()
            .map((e) -> {
                return e * e;
            })
            .collect(Collectors.toList());
    
    |-- 简写形式
    List<Integer> list = ints.stream()
            .map(e -> e * e)
            .collect(Collectors.toList());
    
    ---->[Java中的lambda表达式是什么?]----------------
    |-- 源码:Stream#map------------
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    |-- 可以看出入参是一个Function的类型,有两个泛型 T 和 R
    
    |-- 那Function对象又是什么鬼?
    @FunctionalInterface
    public interface Function<T, R> {
        R apply(T t);
        default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));
        }
    
        default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));
        }
    
        static <T> Function<T, T> identity() {
            return t -> t;
        }
    }
    |-- Functions是一个接口,有两个泛型:T和R ,apply函数出入T类型参数,返回一个R 类型值
     * @param <T> the type of the input to the function 输入的类型
     * @param <R> the type of the result of the function 输出的类型
    |-- 其中有 compose和andThen两个默认的构造接口,看样子compose可以截胡,先走一波before的Function  
    |-- andThen相反,先走自己的apply,然后再走after的apply
    |-- 打个比方,我有一块糖,compose是吃了吐出来再给我吃,andThen是我吃了,吐出来给她吃
    
    |-- 变量提取一下,可以看出这里是一个Function<Integer, Integer>的对象
    Function<Integer, Integer> fn = e -> e * e; 
    fn.apply(8);//64
    fn.compose((Integer e) -> {
                System.out.println();
                return e * 2;
            }).apply(8)//256 = (8*2)^2
    
    fn.andThen((Integer e) -> {
                System.out.println();
                return e * 2;
            }).apply(8));//128 = 8*8 *2
    

    4.JavaScript中的lambda表达式

    类似,也是不会改变原数组

    let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    let result = arr.map(e => {
        return e * e;
    });
    
    console.log(arr);//[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
    console.log(result);//[ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ]
    
    |-- 简写形式:
    let result = arr.map(e => e * e);
    

    5.Python中的lambda表达式

    Python的lambda表达式怎么多行语句...还望指点,网上的都是一行...

    arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    result = map(lambda e: {e * e}, arr)
    print(arr)# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    print(list(result))  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    |-- 简写
    result = map(lambda e: e * e, arr)
    

    6.Dart中的lambda

    var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    var result = arr.map((e) => (e * e));
    print(arr);//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    print(result);//(0, 1, 4, 9, 16, 25, 36, 49, 64, 81)
    
    |-- 简写
    var result = arr.map((e) => e * e);
    

    可见,每种语言对于lambda表达式的表示形式都有区别,
    下面是各语言未简写的完整和简写的lambda表达式

    |-- Kotlin
    val fn = { e: Int -> {
            e * e
        }
    }
    简写:val fn = { e: Int -> e * e }
    
    |-- Java
    Function<Integer, Integer> fn = (Integer e) -> {
        return e * e;
    };
    简写:Function<Integer, Integer> fn =  e -> e * e;
    
    |-- JavaScript
    let fn = (e) => {
        return e * e
    };
    
    简写:let fn = (e) => e * e;
    
    |-- Python
    fn = lambda e: {
        e * e
    }
    简写:fn = lambda e: e * e
    
    |-- Dart
    var fn = (e) => (
        e * e
    );
    简写:var fn = (e) => e * e;
    

    三、从加法来看lambda表达式

    lambda表达式只是函数的一种特别的书写格式,它本身还是函数,可以赋给变量以及调用

    1.Kotlin版

    |-- 加法函数
    fun add(x: Int, y: Int): Int {
        return x + y
    }
    
    |-- 转化为lambda表达式
    val add = { x: Int, y: Int -> { x + y } }
    简写:val add = { x: Int, y: Int ->  x + y }
    |-- 可以将lambda表达式当做普通的函数来调用
    add(3, 5)//8
    
    |-- 再看传入一个函数如参的add方法,它在加之前先对x,y进行处理
    fun add(x: Int, y: Int, fn: (Int) -> Int): Int {
        return fn(x) + fn(y)
    }
    
    |-- 这样就可以计算x,y的平方和:(-3)^2+4^2=25  
    val result = add(-3, 4) { e -> e * e }
    
    |-- 这样就可以计算x,y的绝对值和:|-3|+|4| = 7
    val result = add(-3, 4) { e -> Math.abs(e) }
    |-- 好处不言而喻,可以自定义拓展用法,应你所需
    
    |-- 当然如果你觉得麻烦,就像加一下而已,也可以设个默认值
    fun add(x: Int, y: Int, fn: (Int) -> Int = { e -> e }): Int {
        return fn(x) + fn(y)
    }
    val result = add(-3, 4) //1
    

    2.Java版

    Java中并不像当代语言那么随性,由上面的Function也可以看出,
    是接口让Java支持lambda表达式的,既然Java有Function接口,我们当然也可以自定义

    ---->[定义方法接口]------------------
    public interface AddFun<T, R> {
        R apply(T x, T y);
    }
    
    |-- 使用--------------------
    AddFun<Integer, Integer> add = (x, y) -> x + y;//加法的lambda表达式
    Integer result = add.apply(4, 5);
    
    |-- 如何向上面那样自定义拓展加法呢? 
    |-- 也就是再加一个(函数)入参,可以传入lambda表达式
    public interface AddFun<T, R> {
        R apply(T x, T y, Function<? super T, ? extends R> rule);
    }
    
    AddFun<Integer, Integer> add = (x, y, rule) -> rule.apply(x) + rule.apply(y);//加法的lambda表达式
    Integer result = add.apply(3, 4, e -> e * e);//25
    Integer result = add.apply(-3, 4, e -> Math.abs(e));//7
    Integer result = add.apply(-3, 4, Math::abs);//7  简写
    

    3.JavaScript版

    |-- 加法函数写成lambda表达式
    let la = (x, y) => x + y;
    console.log(la(3, 4));//7
    
    |-- 加法 + lambda表达式入参
    function add(x, y, fn = e => e) {             
        return fn(x) + fn(y);
    }
    
    let a = add(-3, 4, e => e * e);
    let b = add(-3, 4, e => Math.abs(e));
    
    console.log(a);//25
    console.log(b);//7
    
    |-- 合在一起写也可以
    let la = (x, y, fn) => fn(x) + fn(y);
    la(-3, 4,e => e * e);//25
    
    

    4.Python和Dart

    套路都差不多,就不废话了

    |-- Python
    add = lambda x, y: x + y
    addex = lambda x, y, fn: fn(x) + fn(y)
    a = add(3, 4)
    b = addex(-3, -4, lambda e: e * e)
    print(a)#7
    print(b)#25
    
    |-- Dart
    var add = (x, y)=> x + y;
    var addex = (x, y, fn) => fn(x) + fn(y);
    var a = add(3, 4);
    var b = addex(-3, -4, (e)=> e * e);
    print(a);//7
    print(b);//25
    

    四、最后讲几个高阶函数吧

    Java的stream流对集群元素的操作,Kotlin对集群元素的操作,传入函数,使用lambda表达式很方便
    另外JavaScript,Python,Dart操作集群时或多或少都会涉及这些forEach,map,all,any,reduce等。

    1.Java的stream

    |-- forEach操作:遍历元素
    ints.stream().forEach(e->{
        System.out.println(e);
    });
    
    |-- allMatch操作:根据条件控制遍历,看是否全部符合条件,只要有一个不合格,中断遍历并返回false
    List<Integer> ints = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
    Stream<Integer> stream = ints.stream();
    boolean b = stream.allMatch(e -> {
        System.out.println(e);
        return e < 5; //0 1 2 3 4 5
    });
    System.out.println(b);//false 返回是否全部都符合要求
    
    |-- anyMatch操作:根据条件控制遍历,看是否有符合条件,只要有一个合格,中断遍历并返回true
    boolean has = ints.stream().anyMatch(e -> {
        System.out.println(e);
        return e > 5; //0 1 2 3 4 5 6
    });
    System.out.println(has);//true
    
    |-- noneMatch操作:根据条件控制遍历,看是否有符合条件,只要有一个合格,中断遍历并返回false
    boolean hasNot = ints.stream().noneMatch(e -> {
        System.out.println(e);//0 1 2 3 4 5 6
        return e >5 ;
    });
    System.out.println(hasNot);//false
    
    |-- filter操作:过滤出需要的元素,返回的仍是stream,所以可以连续使用
    ints.stream().filter(e -> e % 2 == 0)
            .forEach(System.out::println);//0 2 4 6 8
    
    |-- map操作:可以将所有的元素按照规则全体变化,返回的仍是stream
    |-- collect操作:将一个stream变成Collector,容器对象
    List<Integer> list = ints.stream()
            .map(e -> e * e)
            .collect(Collectors.toList());
    System.out.println(list);
    
    |-- flatMap操作:将层级结构扁平化。比如有三个小偷,每个人偷了几个东西(集合元素)  
    |-- 然后三个人被警察逮到了,三个人一次将自己偷得东西一个一个摆在桌子上,ok,这就是flatMap
    List<Integer> int0to4 = Arrays.asList(0, 1, 2);
    List<Integer> int3o7 = Arrays.asList(3, 4);
    List<Integer> int4to8 = Arrays.asList(4, 5);
    Stream.of(int0to4, int3o7, int4to8).flatMap(list -> list.stream())
            .forEach(System.out::println);//0 1 2 3 4 4 5
    
    |-- limit操作:截取前n个元素,返回的仍是stream
    |-- skip操作:跳过前n个元素,返回的仍是stream
    ints.stream()
            .limit(6)//截取6个 0,1,2,3,4,5
            .skip(2)//跳过前两个
            .forEach(System.out::println);//2 3 4 5
    
    |-- findFirst:获取流中的第一个元素
    int str =  ints.stream()
            .filter(x->x<-3)//过滤流
            .findFirst()//第一个
            .orElse(10000);//默认值
    System.out.println(str);//4
    
    |-- mapToInt:形成int流,好处在于有额外的API
    IntSummaryStatistics stats = ints.stream().mapToInt((x) -> x).summaryStatistics();
    System.out.println("max : " + stats.getMax());//9
    System.out.println("min : " + stats.getMin());//0
    System.out.println("sum : " + stats.getSum());//45
    System.out.println("ave : " + stats.getAverage());//4.5
    System.out.println("count : " + stats.getCount());//10
    
    |-- max和min操作,两者相反,传入一个比较器,返回一个Optional对象
     int max = ints.stream().max((o1, o2) -> o1 - o2).get();
     int min = ints.stream().min((o1, o2) -> o1 - o2).get();
     System.out.println(max+"--"+min);//9--0
    
    |-- reduce操作:
    Integer reduce = ints.stream().reduce(0, (result, value) -> {
        System.out.println(result + "---" + value);
        return result + value;
    });
    System.out.println(reduce);
    感觉reduce超有意思:感觉的话像贪吃蛇,一个一个吃,但吃下一个之前,吃前一个的效果还在  
    其中第一参是偏移量,可以看成贪吃蛇得初始情况,在此基础上,每遍历一次,吃一个
                0---0                                        4---0
                0---1                                        4---1
                1---2                                        5---2
                3---3                                        7---3
    初始值0      6---4                         初始值4        10---4
                10---5                                       14---5
                15---6                                       19---6
                21---7                                       25---7
                28---8                                       32---8
                36---9                                       40---9
                45                                           49
    

    2.Kotlin

    |-- forEach操作:遍历元素
    ints.forEach {
        print("$it ")//0 1 2 3 4 5 6 7 8 9
    }
    
    |-- all操作:根据条件控制遍历,看是否全部符合条件,只要有一个不合格,中断遍历并返回false
    val b = ints.all {
        println(it);
        it < 5; //0 1 2 3 4 5
    }
    println(b) //false 
    
    |-- any操作:根据条件控制遍历,看是否有符合条件,只要有一个合格,中断遍历并返回true
     val any = ints.any {
         println(it);
         it > 5;////0 1 2 3 4 5 6
     }
     println(any)//true
    
    |-- noneMatch操作:根据条件控制遍历,看是否有符合条件,只要有一个合格,中断遍历并返回false
    val any = ints.none() {
        println(it);
        it > 5;//0 1 2 3 4 5 6
    }
    println(any)//false
    
    |-- filter操作:过滤出需要的元素,不损坏原数组
    ints.filter {
        it % 2 == 0
    }.forEach { print("$it "); }//0 2 4 6 8
    
    |-- map操作:可以将所有的元素按照规则全体变化,返回的仍是stream
    ints.map {
        it * it
    }.forEach { print("$it "); }//0 1 4 9 16 25 36 49 64 81
    
    |-- dropWhile操作:知道满足条件之前的元素都删除
    val list = ints.dropWhile { it < 6 }
    println(list)//[6, 7, 8, 9]
    
    |-- reduce操作:
    val reduce = ints.reduce { result: Int, value: Int ->
        println("$result --- $value")
        result + value
    }
    println(reduce)
    

    最后总结一句:在Java中的lambda表达式表示一个接口对象,在各现代语言表示函数

    var la={x: Int ,y:Int-> x +y}
    println(la is (Int, Int) -> Int)//true
    println(::add is (Int, Int) ->Int)//true
    
    fun add(x: Int, y: Int): Int {
        return x + y
    }
    
    

    关于各语言认识深浅不一,如有错误,欢迎批评指正。如果觉得不错就点下关注+小心心吧~

    相关文章

      网友评论

        本文标题:从五大语言看函数和lambda表达式

        本文链接:https://www.haomeiwen.com/subject/iqkfpqtx.html