最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

环球百事通!读函数式编程思维笔记03_权责让渡

来源:博客园


【资料图】

1.观点

1.1.抽象隐藏了繁杂的细节,只是有时候会连同重要的考虑因素一起隐藏掉

1.2.理解掌握的抽象层次永远要比日常使用的抽象层次更深一层

1.3.交出控制权的观点:放弃对繁琐细节的掌控,关注问题域,而非关注问题域的实现

2.函数式思维的好处

2.1.将低层次细节(如垃圾收集)的控制权移交给运行时,从而消弭了一大批注定会发生的程序错误

2.2.函数式语言的简洁语法和灵活配合,才使递归成为简单可行的代码重用选项之一

2.3.运行时有能力在涉及效率的问题上替我们做决定

2.4.从频繁出现的场景中消灭掉烦人的实现细节

3.闭包(closure)

3.1.一种特殊的函数,在生成的时候,会把引用的变量全部圈到代码块的作用域里,封闭、包围起来(故名闭包)

3.1.1.闭包作为一种对行为的建模手段,让我们把代码和上下文同时封装在单一结构,也就是闭包本身里面,像传统数据结构一样可以传递到其他位置,然后在恰当的时间和地点完成执行

3.2.闭包的每个实例都保有自己的一份变量取值,包括私有变量也是如此

3.2.1.代码块实例从它被创建的一刻起,就持有其作用域内一切事物的封闭副本

3.3.在缺乏闭包特性的旧版Java平台上,Functional Java利用匿名内部类来模仿“真正的”闭包的某些行为,但语言的先天不足导致这种模仿是不彻底的

3.4.当作一种异地执行的机制,用来传递待执行的变换代码

3.5.是推迟执行原则的绝佳样板

3.6.抓住上下文,而非状态

3.6.1.“让运行时去管理状态”

4.柯里化(currying)和函数的部分施用(partial application)

4.1.向一部分参数代入一个或多个默认值的办法来实现的

4.1.1.这部分参数被称为“固定参数”

4.2.柯里化

4.2.1.从一个多参数函数变成一连串单参数函数的变换

4.2.2.结果是返回链条中的下一个函数

4.3.部分施用

4.3.1.通过提前代入一部分参数值,使一个多参数函数得以省略部分参数,从而转化为一个参数数目较少的函数

4.3.2.把参数的取值绑定到用户在操作中提供的具体值上,因而产生一个“元数”(参数的数目)较少的函数

4.4.Groovy

4.4.1.curry()函数实现柯里化

4.5.Clojure

4.5.1.(partial f a1 a2 …)函数

4.5.2.没有将柯里化实现成一种语言特性,相关的场景交由部分施用去处理

4.6.Scala

4.6.1.柯里化

4.6.2.部分施用函数

4.6.3.偏函数

4.6.3.1.PartialFunction trait是为了密切配合语言中的模式匹配特性

4.6.3.2.trait并不生成部分施用函数。它的真正用途是描述只对定义域中一部分取值或类型有意义的函数

4.6.3.3.Case语句是偏函数的一种用法

4.6.3.4.偏函数的参数被限定了取值范围

4.6.3.5.可以把偏函数用在任何类型上,包括Any

4.7.大多数函数式语言都具备柯里化和部分施用这两种特性,但实现上各有各的做法

4.8.用途

4.8.1.函数工厂

4.8.1.1.工厂方法的场合,正适合柯里化(以及部分施用)表现它的才干

4.8.2.Template Method(模板方法)模式

4.8.2.1.在固定的算法框架内部安排一些抽象方法,为后续的具体实现保留一部分灵活性

4.8.3.隐含参数

5.递归

5.1.以一种自相似的方式来重复事物的过程

5.2.对一个不断变短的列表反复地做同一件事,把递归用在这样的场合,写出来的代码就容易理解

5.3.递归操作往往受制平台而存在一些固有的技术限制,因此这种技法绝非万灵药

5.4.但对于长度不大的列表来说,递归操作是安全的

5.5.语言在管理返回值,它从递归栈里收集每次方法调用的返回结果,构造出最终的返回值

5.6.利用递归,把状态的管理责任推给运行时

6.记忆(memoization)

6.1.用更多的内存(我们一般不缺内存)去换取长期来说更高的效率

6.1.1.缓存可以提高性能,但缓存有代价:它提高了代码的非本质复杂性和维护负担

6.1.2.负责编写缓存代码的开发者不仅要顾及代码的正确性,连它的执行环境也要考虑在内

6.1.3.代码中的状态,开发者不仅要费心照应它,还要条分缕析它的一切明暗牵连

6.2.记忆的内容应该是值不可变的

6.3.保证所有被记忆的函数

6.3.1.没有副作用

6.3.2.不依赖任何外部信息

6.4.只有纯(pure)函数才可以适用缓存技术

6.4.1.纯函数是没有副作用的函数

6.4.1.1.它不引用其他值可变的类字段

6.4.1.2.除返回值之外不设置其他的变量

6.4.1.3.其结果完全由输入参数决定

6.4.2.只有在函数对同样一组参数总是返回相同结果的前提下,我们才可以放心地使用缓存起来的结果

6.5.缓存是很常见的一种需求,同时也是制造隐晦错误的源头

6.6.两种情况

6.6.1.类内部缓存

6.6.1.1.类中的缓存就代表类有了状态,所有与缓存打交道的方法都不可以是静态的,于是产生了更多的连锁效应

6.6.2.外部调用

6.7.两种实现方式

6.7.1.手工进行状态管理

6.7.2.采用记忆机制

6.8.在命令式的思路下,开发者是代码的主人(以及一切责任的承担者)

6.9.我们写出来的缓存绝不可能比语言设计者产生的更高效,因为语言设计者可以无视他们给语言定的规矩:开发者无法触碰的底层设施,不过是语言设计者手中的玩物,他们拥有的优化手段和空间是“凡人”无法企及的

6.9.1.上帝视角

6.10.Groovy

6.10.1.先将要记忆的函数定义成闭包,然后对该闭包执行memoize()方法来获得一个新函数,以后我们调用这个新函数的时候,其结果就会被缓存起来

6.10.2.memoizeAtMost(1000)

6.11.Clojure

6.11.1.(memoize )

6.12.Scala

6.12.1.没有直接提供记忆机制,但它为集合提供的getOrElseUpdate()方法已经替我们承担了大部分的实现工作

6.13.Java 8

6.13.1.没有直接提供记忆特性,但只要借助它新增的lambda特性,就可以轻松地实现记忆功能

7.缓求值(lazy evaluation)

7.1.尽可能地推迟求解表达式

7.1.1.昂贵的运算只有到了绝对必要的时候才执行

7.1.2.可以建立无限大的集合,只要一直接到请求,就一直送出元素

7.1.3.按缓求值的方式来使用映射、筛选等函数式概念,可以产生更高效的代码

7.1.4.减少占用的存储空间。假如能够用推导的方法得到后续的值,那就不必预先存储完整的列表了——这是牺牲速度来换取存储空间的做法

7.2.非严格求值(non-strict)的(也叫缓求值,lazy)

7.2.1.常用的非严格求值语言有Haskell

7.3.Totally Lazy框架(Java)

7.4.Groovy

7.4.1.缓求值列表是函数式语言普遍具备的特性

7.4.1.1.LazyList

7.4.2.暂缓初始化昂贵的资源,除非到了绝对必要的时候

7.4.3.可以用来构建无限序列,也就是没有上边界的列表

7.4.4.缓求值列表特别适用于资源的生产成本较高的情况

7.5.Clojure

7.5.1.数据结构都是默认缓求值的

7.6.Scala

7.6.1.没有把一切都默认安排成缓求值的,而是在集合之上另外提供了一层缓求值的视图

7.7.缓求值的字段初始化

7.7.1.Scala

7.7.1.1.val声明前面加上“lazy”字样

7.7.1.1.1.令字段从严格求值变成按需要求值

7.7.2.Groovy

7.7.2.1.抽象语法树(Abstract Syntax Tree,AST)

7.7.2.1.1.@Lazy标注

8.元函数技法

8.1.操纵的对象是函数本身,而非函数的结果

8.2.柯里化

关键词: 函数式语言 抽象层次 存储空间