函数

1 概念

函数式编程,简称FP(Functional Programming)。

1.1 数学含义

y=f(x),用FP的思想解释:主体是“f”(函数,图上的箭头),参数是x,y。

参数,可以是对象,也可以是函数

高阶函数:函数的参数也是函数

映射

1.2 VS 对象

面向对象 --> 狗.吃(食) FP --> 吃(狗,食)
示例
主体 对象 --> 狗 函数 --> 吃
描述 对象“狗” has a method “吃” with param "食" function “吃” with params:狗、食
特点 函数不能单独存在,必须被声明在class里 函数可以独立存在

函数和对象,不存在谁从属于谁,只是思维方式的区别。

  • “对象”,会把混沌的业务拆分成若干固定的模块(架构),然后,再细化。理想情况下,模块设计好后,在实施的过程中,即便出现严重的BUG,也只影响有限的模块。当然,这很难做到。于是,需要持续重构系统。如果模块设计地太粗糙,还需要推倒重来。所以,从对象的角度出发,很自然地会用到很多设计模式,从宏观的角度思考问题,避免代码大范围地糜烂。

  • 函数式思维倾向于把一件事做到极致,恰好可以弥补“对象”的不足。

1.3 编程语言的历史

现代语言通过一层又一层的抽象,封装了琐碎的细节。比如,Java语言的垃圾收集器。每一层抽象,都在吃硬件。

普通的方法,传递的“对象”是“静态”的,而高阶函数传递的“函数”是“动态”的。而且,函数嵌套的代码,可阅读性也差、性能也差、学习曲线陡峭...但是呢,“函数”是把利刃,尤其是在具体的业务场景里,用好“函数”,可以快速解决问题。如果,还是继续用Java的“对象”,或是Java8的lambda,估计人家喝咖啡的时候,你还在加无聊的班。

在kotlin之前,我也用Groovy、Scala写过函数,尤其是Scala。相信我,kotlin的语法更严谨。

函数,用kotlin写啦。没有书么?自己写呢?

2 知识点

2.1 闭包

所有函数都是闭包(忘了在哪里看到的了。感觉差不多,你说呢)

2.1.1 调用外部变量

变量的作用域:全局、局部。

在闭包内部的变量是“局部变量”,但,在闭包内部可以调用外部的变量。代码如下:

context("外部变量number = 1") {
    val number = 1
    on("在闭包中调用外部变量number -- 简化的写法") {
        fun f1() = { number + 1 }
        it("should return 2") {
            assertEquals(2, f1()()) //很奇怪?别急,继续看下面的代码
        }
    }
    on("调用number -- 展开所有的类型") {
        //“()”:没有参数
        //返回值"() -> Int",是个函数
        fun f1(): () -> Int = { number + 1 } 

        it("should return 2") {
            val alsoFunction: () -> Int = f1()
            val value = alsoFunction()
            assertEquals(2, value)
        }
    }
}

在JavaScript中,变量声明的时候,如果不用var修饰,默认是全局的...再次吐槽JavaScript。kotlin没有这个BUG。代码如下:

on("闭包内部的变量"){
    fun f1():() -> Int = {
        val number = 1
        number
    }
    //println(number) 会报错。
    it("should be 1") {
        assertEquals(1, f1()())
    }
}

2.1.2 读取闭包的内部变量

示例有点难。能看懂,还是会有收获的

on("读取闭包的内部变量") {
    fun makeCounter(): () -> Int { //返回值也是函数
        var backing = 0 //声明函数内部的变量
        return fun(): Int { return ++backing } // 匿名函数,可以访问外部变量backing
    }
    it("should all be passed") {
        val counter = makeCounter()
        assertEquals(1, counter())
        assertEquals(2, counter())
        assertEquals(3, counter()) //每次计算,backing都会加1
        
        //顺便学习invoke的用法
        assertEquals(1, makeCounter().invoke())
        assertEquals(1, makeCounter()())
    }
}
  • 场景描述

    临时需要一个计数器,而且,有可能多次使用?

  • Java代码示例

    class Counter{
      private int backing = 0;
      public int getCount(){
        return ++backing;
      }
    }
    
  • Kotlin的另一种实现(介于函数和对象之间)

    delegate,用在这个例子里,有点过了。只是推导哈,验证一些想法,还是有学习的意义的

    on("use delegate 读取闭包的内部变量") {
        var counter: Int by object {
            private var backing: Int = 0
            operator fun getValue(thisRef: Any?, property: KProperty<*>): Int =
                    ++backing
            operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
                backing = value
            }
        }
        it("should all be passed") {
            assertEquals(1, counter)
            assertEquals(2, counter)
            assertEquals(3, counter)
        }
    }
    

2.1.3 函数与闭包

再看几个例子,还是有点难。

describe("闭包的应用") {
    data class Employee(val name: String, val salary: Int) //测试用的model
  
    context("返回值是闭包") {
        fun paidMore(amount: Int): (Employee) -> Boolean = { it.salary > amount }
      
        context("使用闭包,设定标准low level:> 100") {
            val amount = 100
            val isHighPaid = paidMore(amount) //请注意:还是函数
          
            it("should all be passed") {
                assertTrue(isHighPaid(Employee("yuri", amount + 1)))
                assertFalse(isHighPaid(Employee("yuri", amount - 1)))
            }
        }
    }
    context("参数是闭包,且有默认的实现") {
        //Employee.paidMore,kotlin的扩展函数。又扯远了。先尝尝吧
        //这种写法相当于元编程,给Employee添加了函数paidMore
        fun Employee.paidMore(amount: Int, predicate: (Int) -> Boolean = {
            this.salary > it //this特指Employee
        }): Boolean = predicate(amount)
      
        on("use the default implementation") {
            val amount = 100
            fun Employee.isHighPaid() = paidMore(amount)
          
            it("should all be passed") {
                assertTrue(Employee("Alice", amount + 1).isHighPaid())
                assertFalse(Employee("Alice", amount - 1).isHighPaid())
            }
        }
      
        on("override the default implementation") {
            val amount = 100
            fun Employee.isHighPaid() = paidMore(amount) {
                this.salary > it * 2
            }
          
            it("should all be passed") {
                assertTrue(Employee("Bob", amount * 2 + 1).isHighPaid())
                assertFalse(Employee("Bob", amount * 2 - 1).isHighPaid())
            }
        }
    }
}

2.1.4 总结

  • 简单、有效

简单的场景,专门定义一个class & method,感觉有点脱了裤子放屁的意思。
如果业务场景经常变,就需要封装变化的部分了。

注意:你得区分变化的是“结构”还是“细节”

如果是“流程”、“结构”变化了,你需要create new classes
如果是具体的“算法”、“逻辑”,你需要函数、闭包、高阶函数

  • 闭包占用额外的内存

闭包是动态的,通过 2.1.2 的例子,你会发现,闭包的状态一直都在内存里。所以,对性能有特殊要求的场景,你需要对闭包专门调优。

  • 代码阅读性差

使用闭包后,代码可以写得很花,但是,阅读的时候就费劲了。相比groovy、Scala,kotlin的语法已经严谨很多了,至少,回头看自己写的代码,花点时间还是能看懂地。至于阅读的速度,自己练吧。

当然,还有函数和闭包的区别,这个...不要计较了,会用就行。

2.2 偏函数

其中,g(y,z)是“偏函数”

given("闭包: (Int,Int) -> Int") {
    val add = { x: Int, y: Int -> x + y }
    
    it("should all be passed") {
        fun addY(y: Int) = add(1, y) //指定add的参数x=1,得到新的函数addY
        assertEquals(3, addY(2))
    }
}

2.3 柯里化 (Carrying)

柯里化

那么,kotlin支持柯里化吗?且看下面的例子:

describe("柯里化") {
    given("柯里化的闭包: (Int) -> (Int) -> Int") {
        val add = { x: Int ->
            { y: Int ->
                x + y
            }
        }
      
        it("should all be passed") {
            assertEquals(3, add(1)(2))
            val addY = add(1)
            assertEquals(3, addY(2))
        }
    }
    given("不能柯里化的闭包: (Int,Int) -> Int") {
        val add = { x: Int, y: Int -> x + y }
      
        it("should all be passed") {
            assertEquals(3, add(1, 2))
            fun addY(y: Int) = add(1, y)
            assertEquals(3, addY(2))
        }
    }
    given("柯里化的函数:(Int) -> (Int) -> Int") {
        fun add(): (Int) -> (Int) -> Int = { x: Int ->
            { x + it }
        }
      
        it("should all be passed") {
            assertEquals(3, add()(1)(2))
            
            val addY = add()(1) //柯里化+偏函数
            assertEquals(3, addY(2))
        }
    }
    given("不能柯里化的函数:(Int, Int) -> Int") {
        fun add(): (Int, Int) -> Int = { x: Int, y: Int -> x + y }
      
        on("直接调用add函数") {
            it("should be equal") {
                assertEquals(3, add()(1, 2))
            }
        }
        on("使用偏函数,模拟柯里化的效果") { //瞎玩,貌似也没什么效果
            fun addY(y: Int) = add()(1, y) //指定原函数add的参数x=1,得到新的函数addY
            it("should also be equal") {
                assertEquals(3, addY(2))
            }
        }
    }
}

kotlin不支持柯里化,为什么呢?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容