Java 8 函数式编程

FP的基本特性

惰性求值:

所谓的惰性求值,简单来说就是在真正对结果进行操作之前并不计算结果(通俗来说就是先画一个大饼,没有人验收查水表之前不兑现)

高阶函数:

接受函数作为参数或者返回函数的函数

闭包:

起函数的作用并可以像对象一样操作的对象(常和高阶函数搭配使用,作为函数内部与外部之间交流的桥梁--->在命令式/声明式编程中,这一点我们往往不能够实现)

函数作为第一等公民而存在:

很简单,函数就像普通的数据类型一样,不仅可以当做输入也可以当做输出 尽量使用表达式而非语句:"表达式"是一个单纯的运算过程,总是有返回值;"语句"是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。

不修改状态:

不修改变量,也是它的一个重要特点。在其他类型的语言中,变量往往用来保存"状态"(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态。

引用透明性:

简单来说,就是函数对于同样的输入有同样的输出,这也是对FP不修改状态和无状态的佐证。

函数接口

首先介绍两个重要概念:函数接口和lamda表达式。所谓的函数接口,就是有且仅有一个抽象方法的接口。而lamda表达式就是一个匿名方法,常常将其赋值给函数接口以获得该接口的对象。

Java8的函数式编程风格由Stream引进,lamda表达式只是用来获得函数接口对象.

Stream具有三大特点:

采用惰性求值 部分构造 流只能够消费一次结尾函数。

惰性求值前面已经介绍了,所谓的部分构造,指的是当我们遇到结尾函数后,就要开始前面的构造过程了,但是这个过程并不是一下子就完成的,而是一个一个的构造并且计算,这也是我们可以优化的点所在,正是由于其构造的部分性,我们通过调整函数的位置就可以达到优化计算的效果。而所谓的流只能消费一次结尾函数则顾名思义。如果要消费多次,那我们只能先collect成数组,然后生成多个流进行消费。

所谓的中间函数简单来说就是不会导致及时求值的函数,而结尾函数则是会导致及时求值的函数,下面则是中间函数和结尾函数的简单总结。

中间函数

中间函数返回一个流,程序中可以多次地、串接地调用流的 intermediate 操作,因为中间操作如映射map、过滤filter函数返回一个流对象。这类操作具有惰性/ laziness。虽然代码写出了对它们 的调用,但是并没有真正执行。

结尾函数

结尾函数返回一个值或void函数产生一个副作用/side effect。

Scala 函数式编程

Clojure 函数式编程

函数式编程大量使用递归和惰性赋值.

函数对自身进行调用,就形成递归.

惰性赋值,表达式的求值,会被推迟到实际需要时才真正进行.

函数式编程 是一种强调函数必须被当成第一等公民对待, 并且这些函数是“纯”的编程方式。这是受 lambda表达式 启发的。纯函数的意思是同一个函数对于同样同样的参数,它的返回值始终是一样的 — 而不会因为前一次调用修改了某个全局变量而使得后面的调用和前面调用的结果不一样。这使得这种程序十分容易理解、调试、测试。它们没有副作用.

在实际生活中,我们的程序是需要一定的副作用的。Haskel的主力开发Simon Peyton-Jones说:

“到最后,任何程序都需要修改状态,一个没有副作用的程序对我们来说只是一个黑盒, 你唯一可以感觉到的是:这个黑盒在变热。”

我们要控制副作用的范围, 清晰地定位它们,避免这种副作用在代码里面到处都是。

把函数当作“第一公民”的语言可以把函数赋值给一个变量,作为参数来调用别的函数, 同时一个函数也可以返回一个函数。可以把函数作为返回值的能力使得我们选择之后程序的行为。接受函数作为参数的函数我们称为“高阶函数”。从某个方面来说,高阶函数的行为是由传进来的函数来配置的,这个函数可以被执行任意次,也可以从不执行。

函数式语言里面的数据是不可修改的。这使得多个线程可以在不用锁的情况下并发地访问这个数据。因为数据不会改变,所以根本不需要上锁。随着多核处理器的越发流行,函数式语言对并发语言的简化可能是它最大的优点。如果所有这些听起来对你来说很有吸引力而且你准备来学学函数式语言,那么你要有点心理准备。

比较流行的函数式语言有: Clojure , Common Lisp , Erlang , F# , Haskell , ML , OCaml , Scheme , Scala . Clojure和Scala是Java Virtual Machine (JVM)上的语言. 还有一些其它基于JVM的语言: Armed Bear Common Lisp (ABCL) , OCaml-Java and Kawa (Scheme).

函数

Each "operation" in Clojure is implemented as either a function, macro or special form.

定义自己的函数

(defn greeting [name] (str "Hello, " name))

调用:

(greeting "Clojure")

"Hello, Clojure"

惰性序列

使用lazy-seq宏创建惰性序列:

(lazy-seq& body)

我们来看看下面的函数:

 (ns cljbasic)

 (defn foo [x] (prn x "Hello,Clojure!"))

 (prn (foo "LightSword Say:"))

;; 定义自然数序列
 (defn naturals [] (iterate inc 1))

;; 定义奇数序列
 (defn odds [] (filter odd? (naturals)))

;; 定义偶数序列
 (defn evens [] (filter even? (naturals)))

;; 定义斐波那契数列
 (defn fib []
   (defn fib-iter [a b] (lazy-seq (cons a (fib-iter b (+ a b)))))
   (fib-iter 0 1))

这些函数的特点是拥有 Clojure 的“惰性计算”特性,我们可以极其简洁地构造一个无限序列,然后通过高阶函数做任意操作。

测试代码:

;; 操作序列
 (prn (take 10 (naturals)))
 (prn (take 10 (odds)))
 (prn (take 10 (evens)))
 (prn (take 10 (fib)))

;; 计算 1X2, 2X3, 3X4, 4X5, 5X6, 6X7, ...
 (prn (take 10 (map * (naturals) (drop 1 (naturals)))))

运行结果:

"LightSword Say:" "Hello,Clojure!"
nil
(1 2 3 4 5 6 7 8 9 10)
(1 3 5 7 9 11 13 15 17 19)
(2 4 6 8 10 12 14 16 18 20)
(0 1 1 2 3 5 8 13 21 34)
(2 6 12 20 30 42 56 72 90 110)

Process finished with exit code 0

查看函数定义文档

 =>(doc greeting)
-------------------------
user/greeting
([name])
  nil
nil

=>(doc defn)
-------------------------
clojure.core/defn
([name doc-string? attr-map? [params*] prepost-map? body] [name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?])
Macro
  Same as (def name (fn [params* ] exprs*)) or (def
    name (fn ([params* ] exprs*)+)) with any doc-string or attrs added
    to the var metadata. prepost-map defines a map with optional keys
    :pre and :post that contain collections of pre or post conditions.
nil

匿名函数(lambda)

问题描述:

过滤出一个序列中字符数大于2的元素(indexalbe-word).

解决思路:

把句子(序列)split成单词, 然后filter每个单词调用字符数是否大于2(indexable-word?),返回true的那些元素.

程序:


user=> (defn indexable-word? [w] (> (count w) 2))
#'user/indexable-word?

user=> (indexable-word? "good")
true

user=> (filter indexable-word? (str/split "A good day it is" #"\W+"))
("good" "day")

如果我们把函数indexable-word? 直接放到代码块里面实现,我们就称之为"匿名函数". 匿名函数的形式如下:

(fn [param*] body)

一种简写的形式:

#(body)

刚才那段程序可以更加简洁的写成

(filter (fn [w] (> (count w) 2)) (str/split "A good day it is" #"\W+"))
("good" "day")

(filter #(> (count %) 2) (str/split "A good day it is" #"\W+"))

(filter #(> (count %1) 2) (str/split "A good day it is" #"\W+"))

这3种写法都是可以的.

使用map和iterate

上面我们降到斐波那契数列.迭代公式

$$f(n)=f(n-1)+f(n-2) ,f(0)=0,f(1)=1$$

考虑iterate的用法:

(take 10 (iterate (fn [[a b]] [b (+ a b)]) [0 1]) )

([0 1] [1 1] [1 2] [2 3] [3 5] [5 8] [8 13] [13 21] [21 34] [34 55])

取这些序列元组的第一个元素:

(map first (take 10 (iterate (fn [[a b]] [b (+ a b)]) [0 1]) ))
(0 1 1 2 3 5 8 13 21 34)

于是,我们有了斐波那契数列的更简洁的实现:


(defn fib [] (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1]) ))

测试:

(take 10 (fib))
(0 1 1 2 3 5 8 13 21 34)

(take 100 (fib))

ArithmeticException integer overflow  clojure.lang.Numbers.throwIntOverflow (Numbers.java:1501)

取前100个fib序列的时候,元素值的范围溢出了.

我们把序列元素类型改成 BigInteger, 只需要在[0 1] 后面加上N:


(defn fib [] (map first (iterate (fn [[a b]] [b (+ a b)]) [0N 1N]) ))
#'user/fib

(take 100 (fib))
(0N 1N 1N 2N 3N 5N 8N 13N 21N 34N 55N 89N 144N 233N 377N 610N 987N 1597N 2584N 4181N 6765N 10946N 17711N 28657N 46368N 75025N 121393N 196418N 317811N 514229N 832040N 1346269N 2178309N 3524578N 5702887N 9227465N 14930352N 24157817N 39088169N 63245986N 102334155N 165580141N 267914296N 433494437N 701408733N 1134903170N 1836311903N 2971215073N 4807526976N 7778742049N 12586269025N 20365011074N 32951280099N 53316291173N 86267571272N 139583862445N 225851433717N 365435296162N 591286729879N 956722026041N 1548008755920N 2504730781961N 4052739537881N 6557470319842N 10610209857723N 17167680177565N 27777890035288N 44945570212853N 72723460248141N 117669030460994N 190392490709135N 308061521170129N 498454011879264N 806515533049393N 1304969544928657N 2111485077978050N 3416454622906707N 5527939700884757N 8944394323791464N 14472334024676221N 23416728348467685N 37889062373143906N 61305790721611591N 99194853094755497N 160500643816367088N 259695496911122585N 420196140727489673N 679891637638612258N 1100087778366101931N 1779979416004714189N 2880067194370816120N 4660046610375530309N 7540113804746346429N 12200160415121876738N 19740274219868223167N 31940434634990099905N 51680708854858323072N 83621143489848422977N 135301852344706746049N 218922995834555169026N)

results matching ""

    No results matching ""