以下函数式编程有何贵干

惰性求值 Lazy 伊娃(Eva)luation

相似而言成员变量在实例创立时即会让开首化,而惰性求值可以将初叶化的进程推迟至变量的率先不佳拔取,对于成员变量的值需要经大量算的切近日说可以使得加快实例的创建进程。

Scala

lazy val lazyField = {
    var sum = 0
    for (i <- 1 to 100) {
        sum += i
    }
    sum
}

当 Scala 中凡是经机要字 lazy
来表明惰性求值的。在上述例子中定义了一个由 1 加到 100
的惰性变量,在首先破看该变量时那个算过程才会合叫实践。

Java

Supplier<Integer> lazyField = () -> {
    int sum = 0;
    for (int i = 1; i <= 100; i++) {
      sum += i;
    }
    return sum;
};

Java 即便以言语层面尚未提供该效率,可是好透过 Java 8 提供的 Supplier
接口来实现平等的功力。

闭包

闭包是一模一样栽含自由变量的代码块,其极其根本的效应就是是可以壮大局部变量的生命周期。闭包相信广大人数犹死熟稔,在
JavaScript 中闭包无处不在,是同种怪好用而一不注意就会丢掉坑里的特色。

Scala

var factor = 10
factor = factor * 10
val multiplier = (x: Int) => x * factor

如上例子中函数体使用了少于个参数,其中 x 只是那些普通的函数参数,而
factor
则是函数体外定义的一个部分变量,且该变量可以无限制举办改动,所以对
factor 的援使该函数成为了一个闭包。

Java

int factor = 10;
//        factor = factor * 10;
Multiplier multiplier = (x) -> x * factor;

以 Java 中匿名函数只可以引用外部的 final 变量,Java 8 固然可以看略
final
关键字,可是其实依旧没外变更,所以亚句子语句必须注释掉。这吗不怕是说当
Java 中实际是心有余而力不足以自由变量的,因而 Java
是否有实在的闭包一向都是一个争执点,这里就是无Dora了。

高阶函数 High-Order Function

高阶函数指一个函数的参数是另一个函数,或者一个函数的重回值是任何一个函数。

参数为函数

def assert(predicate: () => Boolean) =
    if (!predicate())
        throw new RuntimeException("assert failed")

assert(() => 1 == 2)

上述函数 assert() 接收一个匿名函数 () => 1 == 2
作为参数,本质上是采用了传名调用的风味。

重临值为函数

def create(): Int => Int = {
  val factor = 10
  (x: Int) => x * factor
}

特性

图片 1

函数是均等等于人民

所谓的函数是同样抵国民因的凡当 FP 中,函数可以当从来当做变量的价。

Scala

val add = (x: Int, y: Int) => x + y
add(1, 2)

上述我们定义了一个担用少个整型相加的匿名函数并赋值给变量
add,并且直接拿之变量当前函数举行调用,这当大部面向对象的言语中公仍然无能为力直接这样做的。

Java

interface Adder {
    int add(int x, int y);
}
Adder adder = (x, y) -> x + y;
adder.add(1, 2);

由于 Java
并无是函数式语言,所以不可以直接以函数赋值给变量,由此上述例子中大家利用
SAM 转换到落实类似效用。

柯理化 Currying

柯里化指的是用一个接收六只参数的函数分解变成多独收单个参数的函数的平等栽技术。

比如有那样一个通常的函数

def minus(x: Int, y: Int) = x - y

柯理化后即改成以下形式,一个减法操作让剪切为简单片

def minusCurrying(x: Int)(y: Int) = x - y

调用以上两独函数

minus(5, 3)
minusCurrying(5)(3)

函数式编程,一个一贯以来还深,很非常,分外好的名词。虽然诞生很早吗炒了过多年可从来都未曾招非常可怜的水花,但是近几年来随着多按,分布式,大数据的提升,函数式编程已经广泛投入到了实战中。

正文之后的代码重要因 Java 和 Scala
为主,前者表达什么以非函数式语言中落实函数式风格,后者表达在函数式语言中凡是咋样做的。代码相比简单,无论你是否知道就半派别语言,相信仍可以生轻看懂。此外由于函数式编程这几乎单词太丰盛了,以下且坐
FP 进行简写。

集合操作 Collection

会见操作可以说凡是 FP 中尽常用的一个风味,激进的 FP 拥护者甚至当当用
foreach 替代有循环语词。这么些聚集操作本质上就是基本上个放置的高阶函数。

Scala

val list = List(1, 2, 3)
list.map(i => {
  println(s"before $i")
  i * 2
}).map(i => i + 1)
  .foreach(i => println(s"after $i"))

以上定义了一个分包三独整形的列表,依次对里面每个元素就以 2 后再也加
1,最终举行打印操作。输出结果如下:

before 1
before 2
before 3
after 3
after 5
after 7

好望 FP
中的聚众操作关注之是数我,至于哪些遍历数据立马同一行尽管是付了言语中机制来兑现。相比
for 循环来说就起零星单比分明的独到之处:1.
得水平达防范了原数为改,2.
无用关爱遍历的相继。这样用户可在必要平时以操作放到多线程中一旦不用担心引起局部副功效,编译器也足以当编译时自行对遍历举办深优化。

Java

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.stream()
        .map(i -> {
            System.out.println("before " + i);
            return i * 2;
        }).map(i -> i + 1)
        .forEach(i -> System.out.println("after " + i));

输出

before 1
after 3
before 2
after 5
before 3
after 7

足于以上输出看到对于集合操作 Scala 和 Java 的落实全无一致。

Scala
中一个操作着之有着数据就处理后才流向下一个操作,可以当作每个操作都是一个卡。而
Java 则是默认使用了惰性求值的道,并且概念好接近
Spark(Spark)。其各样集合操作重要分为两栽: transformation 和
action。transformation 即转换操作,所有重临 Stream 对象的函数都是
transformation 操作,该操作不汇合应声施行,而是用履行步骤保存于 Stream
对象被。action 即执行操作,action 没有重临值,调用后会合顿时实施前
Stream 对象被保存的有操作。 map() 这样的哪怕是 transformation
操作,forEach() 就是 action 操作。

尾递归 Tail Recursion

递归我们还领会,就是函数自己调用自己。

概念一个递归函数

def addOne(i: Int) {
    if (i > 3) return
    println(s"before $i")
    addOne(i + 1)
    println(s"after $i")
}

调用以上函数并传到参数 3 会打印如下语句

before 1
before 2
before 3
after 3
after 2
after 1

及时即是递归的为主格局。在历次递归调用时先后都必保留时之主意调用栈,即调用
addOne(2) 时程序必须记住前是怎样调用 addOne(1)
的,这样它们才会于推行完 addOne(2) 后赶回到 addOne(1)
的下一条语句并打印 after 1。由此于 Java
等语言中递归一来影响效用,二来消耗内存,调用次数过多时会师挑起方法栈溢出。

尽管尾递归指的即便是仅仅在函数的最终一个言调用递归。这样的好处是得下群
FP
语言都匡助之尾递归优化依旧吃尾递归消除,即递归调用时向来用函数的调用者传入到下一个递归函数中,并将手上函数弹来栈中,在最终一坏递归调用了后一向重临传入的调用者处使休是回去上等同软递归的调用处。

就此简易的示意图即凡是由于原的

line xxx, call addOne -> addOne(1) -> addOne(2) -> addOne(3) -> addOne(2) -> addOne(1) -> line xxx

优化为

line xxx, call addOne -> addOne(1) -> addOne(2) -> addOne(3) -> line xxx

不过现实中还是来广大人口非绝精晓这种编程范式,觉得就只是是一个逼格较高之名词。我们这边就是来概括介绍一下这活动都洋溢酷劲的有些物。

纯函数 Pure Function

纯函数并无是 FP 的表征,而是 FP
中有些表征的聚合。所谓的纯函数略来讲话就是是函数不克生副效能,保证引用透明。即函数本身不会晤窜参数的价值吗未会师修改函数外的变量,无论执行多少次,同样的输入还相会发一致的输出。

概念三单函数

def add(x: Int, y: Int) = x + y

def clear(list: mutable.MutableList): Unit = {
  list.clear()
}

def random() = Random.nextInt()

如上代码中定义了三独函数,其中 add() 符合纯函数的概念;clear()
会清除传入的 List 的拥有因素,所以无是纯函数;random()
不可能保证每趟调用都暴发相同的输入,所以啊未是纯函数。

尾声

除却上述特性,函数式编程中还有 Monoid,SemiGroup
等较为难精晓的概念,本文暂时未关那么坏,留待有趣味之人活动考察。最后自己想说的凡运用函数式编程的确很坂本,不过基本上了解一种植编程范式对于从码农进化为码农++仍旧这么些有襄助的。

假设你针对以上代码有趣味之说话可一向看
https://github.com/SidneyXu/JGSK
[1]: /img/bVyUGu


作者音信
笔者来自力谱宿云 LeapCloud 团队_UX成员:Sidney Xu【原创】
首发地址:https://blog.maxleap.cn/archives/964
简介:多年后端及运动端支付经历,现任 力谱宿云LeapCloud UX
团队成员,重要从于 Android 相关支出,最近对 Kotlin 和 Ruby
有长远兴趣。

本文简单介绍了一晃函数式编程的各个基本特征,希望可以对准备利用函数式编程的人头自及得入门功效。

偏函数 Partial Function

函数指对具有被定类型的输入,总是有一定项目标输出。

偏函数指对于一些给定类型的输入,可能没相应之出口,即偏函数无法处理为定类型范围外的所有值。

概念一个偏函数

val isEven: PartialFunction[Int, String] = {
  case x if x != 0 && x % 2 == 0 => x + " is even"
}

以上 isEven() 只好处理偶数,对于奇数则无法处理,所以是一个偏函数。

偏函数能够用来责任链模式,每个偏函数仅仅处理部分品种的数码,其它项目标数额由下一个偏函数举行处理。

val isOdd: PartialFunction[Int, String] = {
    case x if x % 2 != 0 => x + " is odd"
}
val other: PartialFunction[Int, String] = {
    case _ => "else"
}
val partial = isEven orElse isOdd orElse other
println(partial(3)) //  3 is odd
println(partial(0)) //  else

一对用 Function Partial Application

函数的组成部分应用倚重的是向阳一个收取多独参数的函数传入部分参数从而赢得一个收受剩余参数的新函数的艺。

比如说有这么一个分包六个参数的函数

def show(prefix: String, msg: String, postfix: String) = prefix + msg + postfix

获部分行使函数

val applyPrefix = show("(", _: String, _: String)
println(applyPrefix("foo", ")")) //  (foo)

val applyPostfix = show(_: String, _: String, ")")
println(applyPostfix("(", "bar")) //  (bar)

以上 applyPrefix() 是应用了 show()
的第一个参数的新函数,applyPostfix() 是应用了 show()
的终极一个参数的初函数。

相关文章