函數(shù)式編程更加強(qiáng)調(diào)程序執(zhí)行的結(jié)果而非執(zhí)行的過(guò)程,倡導(dǎo)利用若干簡(jiǎn)單的執(zhí)行單元讓計(jì)算結(jié)果不斷漸進(jìn),逐層推導(dǎo)復(fù)雜的運(yùn)算,而不是設(shè)計(jì)一個(gè)復(fù)雜的執(zhí)行過(guò)程。 – wiki
例子一 累加運(yùn)算
// sum
List<Integer> nums = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 10);
public static Integer sum(List<Integer> nums){
int result = 0;
for (Integer num : nums) {
result += num;
}
return result;
}
sum(nums); // -> 46
同樣的代碼用 Java8 Stream 實(shí)現(xiàn)
Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 10).stream().reduce(0, Integer::sum);
同樣的代碼用 Clojure 實(shí)現(xiàn)
(apply + [0 1 2 3 4 5 6 7 8 10]) ; -> 46
#_(reduce + [0 1 2 3 4 5 6 7 8 10])
例子二 fabonacci數(shù)列
// java
public static int fibonacci(int number){
if (number == 1) {
return 1;
}
if(number == 2) {
return 2;
}
int a = 1;
int b = 2;
for(int cnt = 3; cnt <= number; cnt++) {
int c = a + b;
a = b;
b = c;
}
return b;
}
// java8
Stream.iterate(new int[]{1, 1}, s -> new int[]{s[1], s[0] + s[1]})
.limit(10)
.map(n -> n[1])
.collect(toList())
// -> [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
// clojure
(->> (iterate (fn [[a b]] [b (+ a b)]) [1 1])
(map second)
(take 10))
; -> (1 2 3 5 8 13 21 34 55 89)
比起命令式的語(yǔ)言,函數(shù)式語(yǔ)言更加關(guān)注執(zhí)行的結(jié)果,而非執(zhí)行的過(guò)程。
函數(shù)式編程的歷史
從Hilbert 23個(gè)數(shù)學(xué)難題談起
1900年,Hilbert 提出了數(shù)學(xué)界懸而未決的10大問(wèn)題,后續(xù)陸續(xù)添加成了23個(gè)問(wèn)題,被稱(chēng)為著名的 Hilbert 23 Problem。針對(duì)其中第2個(gè)決定數(shù)學(xué)基礎(chǔ)的問(wèn)題——算術(shù)公理之相容性,年輕的哥德?tīng)柼岢隽烁绲聽(tīng)柌煌陚涠ɡ恚鉀Q了這個(gè)問(wèn)題形式化之后的前兩點(diǎn),即數(shù)學(xué)是完備的嗎?數(shù)學(xué)是相容的嗎?哥德?tīng)栍脙蓷l定理給出了否定的回答。所謂不完備,即系統(tǒng)中存在一個(gè)為真,但是無(wú)法在系統(tǒng)中推導(dǎo)出來(lái)的命題。比如:U說(shuō):“U在PM中不可證”。雖然和說(shuō)謊者很類(lèi)似,但其實(shí)有明顯的差異。我們可以假設(shè)U為可證,那么可以推出PM是矛盾(不相容)的;但是假設(shè)U不可證,卻推導(dǎo)不出PM是矛盾的。U的含義是在M中不可證,而事實(shí)上,它被證明不可證,所以U是PM中不可證的真命題。基于第一條不完備定理,又可以推導(dǎo)出第二條定理。如果一個(gè)(強(qiáng)度足以證明基本算術(shù)公理的)公理系統(tǒng)可以用來(lái)證明它自身的相容性,那么它是不相容的。
而最后一個(gè)問(wèn)題,數(shù)學(xué)是確定的嗎?也就是說(shuō),存在一個(gè)算法判定一個(gè)給定的命題是否是不確定的嗎(Entscheidungsproblem 確定性問(wèn)題)?這個(gè)問(wèn)題引起了阿隆佐·邱奇和年輕的阿蘭·圖靈的興趣。阿隆佐·邱奇的lambda calculus和圖靈的圖靈機(jī)構(gòu)造出了可計(jì)算數(shù),圖靈的那篇論文 ON COMPUTABLE NUMBERS, WITH AN APPLICATION TO THE ENTSCHEIDUNGSPROBLEM 的意義不在于證明可計(jì)算數(shù)是否可數(shù),而在于證明可判定性是否成立。在1936年他們對(duì)判定性問(wèn)題分別獨(dú)立給出了否定的答案。也就是現(xiàn)在被我們熟知的圖靈停機(jī)問(wèn)題:不存在這樣一個(gè)程序(算法),它能夠計(jì)算任何程序(算法)在給定輸入上是否會(huì)結(jié)束(停機(jī))。圖靈借此發(fā)明了通用圖靈機(jī)的概念,為后來(lái)的馮·諾依曼體系的計(jì)算機(jī)體系提供了理論基礎(chǔ)。
Lambda Calculus
Lambda 表達(dá)式包含三個(gè)要素
- 變量
- lambda 抽象
- lambda 應(yīng)用
據(jù)此我們可以用函數(shù)給出布爾值的定義data BOOL = FALSE | TRUE TRUE = λx.λy.x FALSE = λx.λy.y not = λb.b FALSE TRUE and = λb1.λb2.b1 b2 FALSE or = λb1.λb2.b1 TRUE b2 xor = λb1.λb2.b1 (not b2) b2
自然數(shù)的定義
data NAT = Z | S NAT
0 = λf.λs.s
1 = λf.λs.f s
2 = λf.λs.f f s
succ n = λf.λs.f (n f s)
zero? n = n (λb.FALSE) TRUE
add = succ n1 n2
函數(shù)式編程語(yǔ)言的發(fā)展
在這之后,隨著通用計(jì)算機(jī)的產(chǎn)生,人們發(fā)覺(jué)使用機(jī)器碼寫(xiě)程序太沒(méi)有效率。所以1956年左右,John Buckus發(fā)明了Fortran(FORmula TRANslating 的縮寫(xiě))語(yǔ)言,如果對(duì)編譯原理有了解,那么對(duì)BNF范式就不陌生了。與此同時(shí),John McCarthy 發(fā)明了Lisp語(yǔ)言,現(xiàn)代的Clojure就是Lisp的方言之一。1966年,Niklaus Wirth發(fā)明了Pascal。1969年,Ken Thompson和Dennis Ritchie發(fā)明了C語(yǔ)言,過(guò)程式語(yǔ)言由于其高效和可移植性迅速崛起。1973年,Robin Milner 發(fā)明了ML(Meta Language),后來(lái)演變成了OCaml和Stardard ML。1977年,John Buckus在其圖靈獎(jiǎng)的演講中創(chuàng)造了 Functional Programming 這個(gè)詞。1990年,惰性求值的函數(shù)式編程語(yǔ)言 Haskell 1.0 發(fā)布。
神奇的 Y Combinator
(def Y (fn [f]
((fn [x] (x x))
(fn [x]
(f (fn [y]
((x x) y)))))))
Lisp、ML以及Haskell的關(guān)系
Lisp是動(dòng)態(tài)語(yǔ)言,使用S表達(dá)式
ML和Haskell都是靜態(tài)強(qiáng)類(lèi)型函數(shù)式語(yǔ)言
ML是第一個(gè)使用Hindley-Milner type inference algorithm的語(yǔ)言
Lisp和ML都是call-by-value,但是Haskell則是call-by-name
Lisp和ML都是不純的編程語(yǔ)言,但是Haskell是side effect free的
函數(shù)是一等公民
函數(shù)是一等公民,指的是你可以將函數(shù)作為參數(shù)、返回值、數(shù)據(jù)結(jié)構(gòu)存在,而且不僅可以用函數(shù)名引用,甚至可以匿名調(diào)用。
1. 作為參數(shù)
(map inc [1 2 3 4 5]) ;-> (2 3 4 5 6) ;; inc is an argument
2. 作為返回值
(defn add [num]
(fn [other-num] (+ num other-num))) ;; as return-value
(def add-one (add 1))
(add-one 2) ;-> 3
(defn flip [f] ;; as argument and return-value
(fn [x y]
(f y x)))
3. 數(shù)據(jù)結(jié)構(gòu)
(def dictionary {:a "abandon"}) ;; map is also a function, data is code.
(dictionary :a) ;-> "abandon"
(:a dictionary) ;-> "abandon"
4. 匿名函數(shù)
((fn [x] (* x x))
2) ;-> 4
(map
(fn [num] (+ 1 num)) ;; anonymous function
[1 2 3 4 5]) ;-> (2 3 4 5 6)
5. 模塊化
在面向?qū)ο笾校瑢?duì)象是一等公民。所以我們處處要從對(duì)象的角度去考慮計(jì)算問(wèn)題,然后產(chǎn)生一種共識(shí)——數(shù)據(jù)應(yīng)該和它相關(guān)的操作放到一起,也就是我們所說(shuō)的封裝。確實(shí)沒(méi)錯(cuò),但是我們得知道封裝的意義在哪里?功能內(nèi)聚好理解(分塊)和局部性影響(控制可變性)。函數(shù)式編程同樣考慮這些,功能內(nèi)聚不一定要用類(lèi)的方式(考慮一下JS的prototype,也是一種面向?qū)ο螅灰K做得好,一樣能達(dá)到效果。局部性影響,其本質(zhì)是封裝可變因素以避免其擴(kuò)散到代碼各處。函數(shù)式給出了自己的答案,消除可變因素。
高階函數(shù)和惰性求值也非常有利于模塊化。
純函數(shù)和不可變性
純函數(shù)是指執(zhí)行過(guò)程中沒(méi)有副作用的函數(shù),所謂副作用是說(shuō)超出函數(shù)控制的操作,比如在執(zhí)行過(guò)程中操作文件系統(tǒng)、數(shù)據(jù)庫(kù)等外部資源。純函數(shù)還具有引用透明性的特點(diǎn),也就是同樣的輸入導(dǎo)致同樣的輸出,以至于完全可以用函數(shù)的值代替對(duì)函數(shù)的調(diào)用。
引用透明
舉個(gè)例子:
(inc 1) ; -> 2
(= (inc (inc 1)
(inc 2))) ; -> true
你們可能就會(huì)問(wèn),這種東西究竟有什么用呢?純函數(shù)可以很方便地進(jìn)行緩存。
(defn fibonacci [number]
(if (or (zero? number) (= 1 number)) 1
(+
(fibonacci (dec number))
(fibonacci (- number 2)))))
(fibonacci 30) ; -> "Elapsed time: 185.690208 msecs"
(def fibonacci
(memoize (fn [number] ;;
(if (or (zero? number) (= 1 number)) 1
(+
(fibonacci (dec number))
(fibonacci (- number 2)))))))
(fibonacci 30) ; -> "Elapsed time: 0.437114 msecs"
不可變計(jì)算
談到不可變性,我們做個(gè)游戲。統(tǒng)計(jì)在座的一共有多少人數(shù)。我們都知道從某個(gè)人開(kāi)始依次報(bào)數(shù),最后得到的數(shù)字就是總?cè)藬?shù),其實(shí)這就是一種不可變計(jì)算的游戲,為什么這么說(shuō)呢?因?yàn)閳?bào)數(shù)其實(shí)一個(gè)計(jì)算的過(guò)程,第一個(gè)人計(jì)算出1這個(gè)數(shù),傳遞給第二個(gè)人。然后第二個(gè)人拿著前面的1進(jìn)行加一操作,然后把結(jié)果2傳遞給后面的人做加法,以此類(lèi)推。為了提高統(tǒng)計(jì)的效率,我也可以進(jìn)行分組,然后每組自行報(bào)數(shù),最后統(tǒng)計(jì)結(jié)果。但是如果我在白板上寫(xiě)個(gè)數(shù)字1,然后讓大家來(lái)過(guò)來(lái)該這個(gè)數(shù)字,很大可能會(huì)出現(xiàn)錯(cuò)誤,因?yàn)檫@個(gè)數(shù)字成為了競(jìng)態(tài)條件。在多并發(fā)的情況下,就得用讀寫(xiě)鎖來(lái)控制。所以不可變性特別利于并發(fā)。
不可變的鏈?zhǔn)浇Y(jié)構(gòu)
好了,現(xiàn)在我們有個(gè)新的需求,設(shè)計(jì)一個(gè)不可變列表收集大家的名字。每個(gè)節(jié)點(diǎn)存儲(chǔ)一個(gè)姓名的字符串,并且有個(gè)指針指向下一個(gè)節(jié)點(diǎn)。但是這也打破了列表的不可變性。怎么辦?我們可以把新的節(jié)點(diǎn)指向舊有的列表,然后返回一個(gè)新的列表。這就是不可變列表實(shí)現(xiàn)的機(jī)制。隨便一提,這也是區(qū)塊鏈不可變特征的由來(lái)。
Clojure的創(chuàng)造者Rich Hickey擴(kuò)展了Ideal Hash Tree數(shù)據(jù)結(jié)構(gòu),實(shí)現(xiàn)了Persistent Vector。由于此處的葉子節(jié)點(diǎn)可以擴(kuò)展成32個(gè),所以可以大量存儲(chǔ)數(shù)據(jù)。利用Ideal Hash Tree的特點(diǎn)可以快速索引出數(shù)據(jù),與此同時(shí),數(shù)據(jù)的“增刪改”也能做到近常數(shù)化的時(shí)間,并且總是產(chǎn)生新的數(shù)據(jù)結(jié)構(gòu)替換原有的數(shù)據(jù)結(jié)構(gòu),即一種不可變的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)。
不可變的樹(shù)狀結(jié)構(gòu)
Zipper數(shù)據(jù)結(jié)構(gòu)類(lèi)似于文本編輯器中的 gap buffer,編輯文本時(shí),光標(biāo)左邊和右邊分別是獨(dú)立的buffer,光標(biāo)處也是單獨(dú)的buffer,這樣便可以方便地添加文字,也很方便刪除左右buffer中的文字;移動(dòng)光標(biāo)會(huì)涉及buffer之間的拷貝。基本上能在常數(shù)時(shí)間內(nèi)完成編輯。Zipper數(shù)據(jù)結(jié)構(gòu)模仿了這種方式,能在常數(shù)時(shí)間內(nèi)完成樹(shù)的編輯工作,也能很快地重新構(gòu)建一棵樹(shù)。
遞歸
可計(jì)算很大問(wèn)題就是得實(shí)現(xiàn)遞歸功能。
(defn reverse-seq [coll]
(when-let [elem (first coll)]
(concat (reverse-seq (rest coll)) [elem])))
(reverse-seq [1 2 3]) ; -> (3 2 1)
和循環(huán)無(wú)異的尾遞歸
(defn gcd [& nums]
(reduce #(if (zero? %2)
%
(recur %2 (mod % %2))) nums))
(gcd 8 16) ; -> 8
生成式測(cè)試
生成式測(cè)試會(huì)基于輸入假設(shè)輸出,并且生成許多可能的數(shù)據(jù)驗(yàn)證假設(shè)的正確性。
(defn add [a b]
(+ a b))
;; 任取兩個(gè)整數(shù),把a(bǔ)和b加起來(lái)的結(jié)果減去a總會(huì)得到b。
(def test-add
(prop/for-all [a (gen/int)
b (gen/int)]
(= (- (add a b) a) b)))
(tc/quick-check 100 test-add)
; -> {:result true, :num-tests 100, :seed 1515935038284}
測(cè)試結(jié)果表明,剛才運(yùn)行了100組測(cè)試,并且都通過(guò)了。理論上,程序可以生成無(wú)數(shù)的測(cè)試數(shù)據(jù)來(lái)驗(yàn)證add方法的正確性。即便不能窮盡,我們也獲得一組統(tǒng)計(jì)上的數(shù)字,而不僅僅是幾個(gè)純手工挑選的用例。
抽象是什么
抽取共性,封裝細(xì)節(jié),忘記不重要的差異點(diǎn)。這樣的好處是可以做到局部化影響和延遲決策。
命名
命名就是一種抽象,重構(gòu)中最重要的技法就是重命名和提取小函數(shù)
(* 3 3 3)
(* x x x)
(* y y y)
->
(defn cube [x]
(* x x x))
延遲決策
例如:我們定義數(shù)對(duì) pair
pair:: (cons x y)
firstpair -> x
secondpair -> y
那么它的具體實(shí)現(xiàn)會(huì)是這樣的
(defn cons [x y]
(fn [m]
(cond (= m 0) x
(= m 1) y)))
(defn first [z]
(z 0))
(defn second [z]
(z 1))
也可以是這樣的,還可以是其它各種各樣的形式。
(defn cons [x y]
(fn [b]
(b x y))
(defn first [z]
(z (fn [x y] x)))
(defn second [z]
(z (fn [x y] y)))
高階函數(shù)
高階函數(shù)就是可以接收函數(shù)的函數(shù),高階函數(shù)提供了足夠的抽象,屏蔽了很多底層的實(shí)現(xiàn)細(xì)節(jié)。比如Clojure中的 map 高階函數(shù),它接收 (fn [v] ...) ,把一組數(shù)據(jù)映射成另外一組數(shù)據(jù)。
過(guò)程抽象
(map inc [1 2 3 4 5]) ; -> (2 3 4 5 6)
這些函數(shù)抽象出映射這樣語(yǔ)義,除了容易記憶,還能很方便地重新編寫(xiě)成高效的底層實(shí)現(xiàn)。也就是說(shuō),一旦出現(xiàn)了更高效的 map 實(shí)現(xiàn)算法,現(xiàn)有的代碼都能立刻從中受益。
函數(shù)的組合
函數(shù)組合之后會(huì)產(chǎn)生巨大的能量
神奇的加法
(((comp (map inc) (filter odd?)) +) 1 2) ; -> 4
怎么去理解這個(gè)函數(shù)的組合?我們給它取個(gè)好聽(tīng)的名字
(def special+ ((comp (map inc) (filter odd?)) +))
(special+ 1 2) ; -> 4
; <=> 等價(jià)于
(if (odd? (inc 2))
(+ 1 3))
1)
這個(gè)未必是個(gè)好的組合方式,但是不可否認(rèn)的是,我們可以用這些隨意地將這些函數(shù)組合到一起,得到我們想要的結(jié)果。
transducer
(def xf (comp (filter odd?) (take 10)))
(transduce xf conj (range))
;; [1 3 5 7 9 11 13 15 17 19]
這里直接將求值延遲到了 transduce 計(jì)算的時(shí)候,換句話(huà)說(shuō), xf 定義了一種過(guò)程:filter出奇數(shù)并取出前10個(gè)元素。同等的代碼,如果用表達(dá)式直接書(shū)寫(xiě)的話(huà),如下:
(->> (range)
(filter odd?)
(take 10))
這里的問(wèn)題就是我們沒(méi)能使用高階函數(shù)抽象出過(guò)程,如果把 conj 換成其他的reduce運(yùn)算,現(xiàn)在的過(guò)程無(wú)法支撐,但是tranducers可以!
(transduce xf + (range)) ;-> 100
我們?cè)倏匆粋€(gè)tranducer的神奇使用方式:
(defn log [& [idx]]
(fn [rf]
(fn
([] (rf))
([result] (rf result))
([result el]
(let [n-step (if idx (str "Step: " idx ". ") "")]
(println (format "%sResult: %s, Item: %s" n-step result el)))
(rf result el)))))
(def ^:dynamic *dbg?* false)
(defn comp* [& xforms]
(apply comp
(if *dbg?*
(->> (range)
(map log)
(interleave xforms))
xforms)))
(binding [*dbg?* true]
(transduce
(comp*
(map inc)
(filter odd?))
+
(range 5))) ;; -> 9
Step: 0. Result: 0, Item: 1
Step: 1. Result: 0, Item: 1
Step: 0. Result: 1, Item: 2
Step: 0. Result: 1, Item: 3
Step: 1. Result: 1, Item: 3
Step: 0. Result: 4, Item: 4
Step: 0. Result: 4, Item: 5
Step: 1. Result: 4, Item: 5
之所以會(huì)出現(xiàn)上述的結(jié)果,是因?yàn)?interleave xforms 將 (map inc) 以及 (filter odd?) 和logs進(jìn)行了交叉,得到的結(jié)果是 (comp (map inc) (log) (filter odd?) (log)) ,所以如果是偶數(shù)就會(huì)被filter清除,看不見(jiàn)log了。
首先一定得理解:每個(gè)tranducer函數(shù)都是同構(gòu)的!
形式如下
(defn m [f]
(fn [rf]
(fn [result elem]
(rf result (f elem)))))
這意味著 (m f) 的函數(shù)都是可以組合的,組合的形式如下:
(comp (m f) (m1 f1) ...)
展開(kāi)之后
((m f)
((m1 f1)
((m2 f2) ...)))
->
(fn [result elem]
(((m1 f1)
((m2 f2) ...)) result (f elem)))
所以可以看到第一個(gè)執(zhí)行的一定是 comp 的首個(gè) reducing function 參數(shù)。故:
- xform 作為組合的前提
- 執(zhí)行順序從左到右;
- + 作為 reducing function 最后執(zhí)行;
Monad
什么是 Monad 呢?A monad is just a monoid in the category of endofunctors.
- Identity—For a monad m, m flatMap unit => m
- Unit—For a monad m, unit(v) flatMap f => f(v)
- Associativity—For a monad m, m flatMap g flatMap h => m flatMap {x => g(x) flatMap h}
// java8 實(shí)現(xiàn)的 9*9 乘法表 public class ListMonad<T>{ private List<T> elements; private ListMonad(T elem) { this.elements = singletonList(elem); } private ListMonad(List<T> elems) { this.elements = elems; } public <U> ListMonad<U> flatmap(Function<T, ListMonad<U>> fn) { List<U> newElements = new ArrayList<>(); this.elements.forEach(elem -> newElements.addAll(fn.apply(elem).elements)); return new ListMonad<>(newElements); } public <X> ListMonad<X> uint(X elem) { return new ListMonad<>(elem); } public <U> ListMonad<U> apply(ListMonad<Function<T, U>> m) { return m.flatmap(this::map); } public <U> ListMonad<U> map(Function<T, U> fn) { return flatmap(t -> uint(fn.apply(t))); } public static void main(String[] args) { ListMonad<Integer> m = new ListMonad<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); ListMonad<Integer> m1 = new ListMonad<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); ListMonad<Integer> list = m.apply(m1.map(x -> y -> x * y)); // [1...81] } }
表達(dá)式優(yōu)于語(yǔ)句
S表達(dá)式
- 原子,或者;
- 形式為 (x ? y) 的表達(dá)式,其中x和y也是S表達(dá)式。
舉個(gè)例子,遞增一組數(shù)據(jù),過(guò)濾奇數(shù),然后進(jìn)行排序,最終取出第一個(gè)。如果取不到,返回 :not-found 。
(-> [1 2 3]
(->> (map inc)
(filter odd?)
(sort)
(first))
(or :not-found))
; -> 3
(-> [1 1 3]
(->> (map inc)
(filter odd?)
(sort)
(first))
(or :not-found)
; -> :not-found
當(dāng)然你也可以寫(xiě)成
(if-let [r (first (sort (filter odd? (map inc [1 1 1]))))]
r
:not-found)
; -> :not-found
其實(shí)兩者都是S表達(dá)式,但是下面的寫(xiě)法更加偏向于語(yǔ)句。從串聯(lián)起來(lái)讀來(lái)講,前者明顯是由于后者的。這要是放在其他函數(shù)式語(yǔ)言上,效果更加顯著。比如下面重構(gòu)if-else控制語(yǔ)句到Optional類(lèi)型。
if-else -> Optional
Optional<Rule> rule = ruleOf(id);
if(rule.isPresent()) {
return transform(rule.get());
} else {
throw new RuntimeException();
}
public Rule transform(Rule rule){
return Rule.builder()
.withName("No." + rule.getId())
.build();
}
這是典型的語(yǔ)句可以重構(gòu)到表達(dá)式的場(chǎng)景,關(guān)鍵是怎么重構(gòu)呢?
第一步,調(diào)轉(zhuǎn) if 。
Optional rule = ruleOf(id);
if(!rule.isPresent()) {
throw new RuntimeException();
}
return transform(rule.get());
第二步, Optional.map 函數(shù)
...
return rule.map(r -> transform(r)).get();
第三步, inline transform 函數(shù)
...
rule.map(r -> Rule.builder()
.withName("No." + r.getId())
.build()).get();
第四步, Optional.orElseThrow 函數(shù)
...
rule.map(r-> Rule.builder()
.withName("No." + r.getId())
.build())
.orElseThrow(() ->new RuntimeException());
第五步,注 if 釋語(yǔ)句中的 throw new RuntimeException()
if(!rule.isPresent()) {
// throw new RuntimeException();
}
這時(shí)候發(fā)現(xiàn)語(yǔ)句中為空,即可將整個(gè)語(yǔ)句刪除。可以考慮 inline rule 。
ruleOf(id).map(r-> Rule.builder()
.withName("No." + r.getId())
.build())
.orElseThrow(() ->new RuntimeException());
完畢。
我們認(rèn)識(shí)事物的方式
- 把幾個(gè)簡(jiǎn)單的想法合并成一個(gè)復(fù)合概念,從而創(chuàng)造出所有復(fù)雜的概念。
- 簡(jiǎn)單的或復(fù)雜的兩種思想融合在一起,并立即把它們聯(lián)系起來(lái),不要把它們統(tǒng)一起來(lái),從而得到它所有的關(guān)系思想。
- 把他們與其他所有陪伴他們的真實(shí)存在的想法分開(kāi):這就是所謂的抽象,因此所有的一般想法都是被提出來(lái)的。
來(lái)自:https://lambeta.com/2018/02/17/The-Simple-Summary-of-FP/
掃碼二維碼 獲取免費(fèi)視頻學(xué)習(xí)資料
- 本文固定鏈接: http://phpxs.com/post/5934/
- 轉(zhuǎn)載請(qǐng)注明:轉(zhuǎn)載必須在正文中標(biāo)注并保留原文鏈接
- 掃碼: 掃上方二維碼獲取免費(fèi)視頻資料