V**r 使函式式程式設計更簡單、更易於使用。
在本系列的上一篇文章中,我們介紹了 J**a 平台提供的 lambda 表示式和流。 受 J**A 標準庫的通用性要求和二進位檔案大小的限制,J**a 標準庫對函式式程式設計的 API 支援相對有限。 函式宣告只有兩種型別:函式和雙函式,流上支援的操作數量也很少。 為了更好的函式式程式設計,我們需要第三方庫的支援。 V**R 是 J**a 平台上最好的函式式程式設計庫。 許多開發人員可能不熟悉 v**r 這個名字。 它的前身 J**Aslang 可能更熟悉。 V**r 作為標準的 j**a 庫,使用簡單。 只需新增對io.v**r:v**r
可以完成庫的 m**en 依賴項。 v**r 需要支援 J**a 8 及更高版本。 本文基於 v**r 09.版本 2,基於 Ja 10 的示例。
元組是具有固定數字的不同型別的元素的組合。 元組與集合的不同之處在於,元組中的元素型別可以不同,並且數量是固定的。 元組的好處是可以將多個元素作為乙個單元傳遞。 如果方法需要返回多個值,則可以將這些值作為元組返回,而無需建立其他類來表示它們。 根據元素的數量,v**r 總共提供 9 個類,例如 tuple0、tuple1 到 tuple8。 每個元組類都需要宣告其元素型別。
如:tuple2
它表示兩個元素的元組,第乙個元素是字串型別,第二個元素是整數型別。 對於元組物件,您可以使用它自
以訪問其中的元素。 所有元組物件都是不可變的,建立後無法更改。
元組由介面元組的靜態方法建立。 元組類還提供了一些操作它們的方法。 由於元組是不可變的,因此所有相關操作都會返回乙個新的元組物件。 在清單 1 中,使用 tupleof 建立乙個 Tuple2 物件。
Tuple2 的 Map 方法用於轉換元組中的每個元素,返回乙個新的元組物件。 另一方面,apply 方法將元組轉換為單個值。 其他元組類也有類似的方法。 除了map
除了方法之外,還有map1、map2、map3等方法來轉換第n個元素; update1、update2 和 update3 等方法用於更新各個元素。
清單 1使用元組。
tuple2 tuple2 = tuple.of("hello", 100);tuple2 updatedtuple2 = tuple2.map(string::touppercase, v ->v * 5);string result = updatedtuple2.apply((str, number) -string.join(", ",str, number.tostring())system.out.println(result);
雖然元組使用起來很方便,但它們不應該被濫用,尤其是當它們具有 3 個以上的元素時。 當元組中的元素過多時,可能很難明確地記住每個元素的位置和含義,從而降低其可讀性。 此時,最好使用 j**a 類。
在 J**A 8 中,只提供乙個接受乙個引數的函式和乙個接受兩個引數的雙函式。 v**r 提供函式介面 function0、function1 到 function8,可以描述最多接受 8 個引數的函式。 這些介面的方法apply
不能引發異常。 如果需要丟擲異常,可以使用相應的 API:checkedfunction0、checkedfunction1 和 checkedfunction8。
v**r 的函式支援一些常見的特徵。
函式組合是指利用乙個函式的執行結果作為引數呼叫另乙個函式而得到的新函式。 例如,f 是從 x 到 y 的函式,g 是從 y 到 z 的函式,那麼g(f(x))
是乙個從 x 到 z 的函式。 v**r 的功能介面提供了預設方法andthen
將當前函式與另乙個函式表示的函式組合在一起。 v**r 的 function1 還提供了乙個預設方法 compose,用於在執行當前函式之前執行由另乙個函式表示的函式。
在清單 2 中,第乙個函式 3 執行簡單的數學運算,然後使用 然後將函式 3 的結果乘以 100。 第二個 function1 來自字串touppercase
建立和使用的方法compose
帶有物件的方法tostring
首先結合方法。 首先為任何物件呼叫生成的方法tostring
然後再次致電touppercase
清單 2功能組合。
function3< integer, integer, integer, integer> function3 = (v1, v2, v3)->v1 + v2) *v3;function3< integer, integer, integer, integer> composed =function3.andthen(v ->v * 100);int result = composed.apply(1, 2, 3);system.out.println(result);輸出 900function1< string, string> function1 = string::touppercase; function1< object, string> touppercase = function1.compose(object::tostring);string str = touppercase.apply(list.of("a", "b"));system.out.println(str);輸出 [a, b]。
在 v**r 中,函式的 apply 方法可以應用不同數量的引數。 如果提供的引數數小於函式宣告的引數數(passarity()
method),則結果為另乙個函式,所需的引數數是剩餘未指定值的引數數。在清單 3 中,function4 接受 4 個引數,在apply
呼叫時僅提供 2 個引數,結果為 function2 物件。
清單 3部分功能應用。
function4< integer, integer, integer, integer, integer> function4 =(v1, v2, v3, v4) -v1 + v2) *v3 + v4);function2< integer, integer, integer> function2 = function4.apply(1, 2);int result = function2.apply(4, 5);system.out.println(result);輸出 27
用curried
方法獲取當前函式的咖哩化版本。 由於 curryized 函式只有乙個引數,curried
都是 function1 物件。 在清單 4 中,對於 function3,在第乙個curried
方法呼叫獲取 function1 後,它通過apply
將值應用於第乙個引數。 以此類推,通過 3 次curried
跟apply
呼叫,將值應用於所有 3 個引數。
清單 4函式的咖哩化。
function3 function3 = (v1, v2, v3)->v1 + v2) *v3;int result =function3.curried().apply(1).curried().apply(2).curried().apply(3);system.out.println(result);
使用記憶函式會根據引數值快取先前計算的結果。 對於相同的引數值,另乙個呼叫將返回快取的值,而無需再次計算。 這是乙個經典的時空策略。 可以使用記憶的前提是該函式具有引用透明度。
在清單 5 中,使用了原始函式實現biginteger
之pow
計算功率的方法。 用memoized
方法獲取函式的記憶版本。 然後呼叫相同的引數兩次並記錄時間。 從結果中可以看出,第二個函式呼叫需要很短的時間,因為結果是直接從快取中獲取的。
清單 5功能記憶。
function2 pow = biginteger::pow;function2 memoized = pow.memoized();long start = system.currenttimemillis();memoized.apply(biginteger.valueof(1024), 1024);long end1 = system.currenttimemillis();memoized.apply(biginteger.valueof(1024), 1024);long end2 = system.currenttimemillis();system.out.printf("%d ms ->d ms", end1 - start, end2 - end1);
注意memoized
該方法只是將原始函式視為黑盒,並且不修改函式的內部實現。 因此memoized
它不適用於本系列第二篇文章中遞迴計算斐波那契數列的函式的直接封裝。 這是因為在函式的內部實現中,函式仍然是在沒有記憶體的情況下呼叫的。 v**r 中提供了幾種不同型別的值。
v**roption
帶 J**A 8optional
是相似的。 但是,v**r 的選項是具有兩個實現類的介面option.some
跟option.none
,分別對應兩種情況,有值和無值。 用option.some
可以建立包含給定值的方法some
物件,而option.none
是的none
物件的例項。 option
還支援常用的map
flatmap
跟filter
依此類推,如清單 6 所示。
清單 6使用選項的示例。
option str = option.of("hello");str.map(string::length);str.flatmap(v ->option.of(v.length())
either
指示可能有兩種不同型別的值,稱為左值或右值。 它只能是其中之一。 either
它通常用於表示成功或失敗。 慣例是將成功的值作為右值,將失敗的值作為左值。 是的, 你可以的either
新增適用於左值或右值的計算。 適用於右值的計算僅在either
它僅在包含右值時生效,左值也是如此。
在清單 7 中,基於隨機布林值建立乙個 LOR LVALUEeither
物件。 either
之map
跟mapleft
右值和左值是分開計算的。
清單 7使用兩者之一的示例。
import io.v**r.control.either;import j**a.util.concurrent.threadlocalrandom;public class eithers private static either compute()
try
用於表示可能產生異常的計算。 try
該介面有兩個實現類try.success
跟try.failure
,分別代表成功和失敗。 try.success
在計算成功時封裝返回值,而try.failure
然後封裝計算失敗throwable
物件。 可以從介面獲取 try 的例項checkedfunction0
callable
runnable
或supplier
。 try
還提供map
跟filter
等方法。 值得一提的是try
之recover
在發生錯誤時從異常中恢復的方法。
在清單 8 中,第乙個try
是的結果顯然是一種反常現象。 用
recover
返回 1。 第二個try
它表示讀取檔案的結果。 由於該檔案不存在,try
這也是一種反常現象。
清單 8使用 try 的示例。
try result = try.of(()1 / 0).recover(e ->1);system.out.println(result);try lines = try.of(()files.readalllines(paths.get("1.txt")))map(list ->string.join(",", list)) andthen((consumer) system.out::println);system.out.println(lines);
lazy
表示延遲計算的值。 評估操作在第一次訪問時發生,並且僅評估一次值。 後續訪問操作獲取快取值。 在清單 9 中,lazy.of
從站介面supplier
。 lazy
物件。 方法:isevaluated
可以判斷lazy
物件是否已被評估。
清單 9使用懶惰的示例。
lazy lazy = lazy.of(()biginteger.valueof(1024).pow(1024));system.out.println(lazy.isevaluated())system.out.println(lazy.get())system.out.println(lazy.isevaluated())
v**r 在 Iterable 之上重新實現自己的收集框架。 v**r 的收集框架側重於不變性。 v**r 的集合類比 j**a 流更簡潔易用。
V**r 的流比 j**a 中的流提供更多的操作。 可以使用stream.ofall
從可迭代物件建立 v**r 流。 以下是 v**r 中新增的一些有用操作:
groupby
:使用功能對元素進行分組。 結果是乙個對映,其中對映的鍵是組函式的結果,值是包含同一組中所有元素的流。 partition
:使用謂詞對元素進行分組。 結果是具有 2 個流的 tuple2。 tuple2 的第乙個流的元素滿足謂詞指定的條件,第二個流的元素不滿足謂詞指定的條件。 scanleft
跟scanright
:分別按從左到右或從右到左的順序對元素呼叫函式,並累加結果。 zip
:將流與可迭代物件合併,生成的流包含 tuple2 物件。 tuple2 物件的兩個元素分別來自流物件和可迭代物件。 在清單 10 中,第乙個groupby
該操作將流分為奇陣列和偶數組; 第二個partition
該操作將流分為兩組:大於 2 且不大於 2; 第三scanleft
包含字串的流根據字串的長度進行累積; 最後乙個zip
該操作合併兩個流,結果是與長度最小的輸入流具有相同元素數的流。
清單 10Stream的使用示例。
map> booleanlistmap = stream.ofall(1, 2, 3, 4, 5) .groupby(v ->v % 2 == 0) .mapvalues(value::tolist);system.out.println(booleanlistmap);輸出 linkedhashmap((false, list(1, 3, 5)),true, list(2, 4))))tuple2, list>listtuple2 = 流ofall(1, 2, 3, 4) .partition(v ->v > 2) .map(value::tolist, value::tolist);system.out.println(listtuple2);輸出 (list(3, 4), list(1, 2))list integers = stream.ofall(list.of("hello", "world", "a")) scanleft(0, (sum, str) -sum + str.length())tolist();system.out.println(integers);輸出 list(0, 5, 10, 11)list> tuple2list = 流ofall(1, 2, 3) .zip(list.of("a", "b")) tolist();system.out.println(tuple2list);輸出列表((1, a), 2, b)))。
v**r 提供了常見資料結構的實現,包括 list、set、map、seq、queue、tree 和 treemap。 這些資料結構的用法類似於 J**a 標準庫的用法,但提供了更多操作並且更易於使用。 在 j**a 中,如果需要對映列表的某個元素,則需要使用 stream 方法將其轉換為流,然後使用 map 操作,最後通過收集器collectors.tolist
轉換回列表。 在 v**r 中,列表本身提供對映操作。 這兩種用途的區別如清單 11 所示。
清單 11v**r 中資料結構的用法。
list.of(1, 2, 3).map(v ->v + 10); 2, 3).stream().map(v ->v + 10).collect(collectors.tolist()) j**a
在 j**a 中,我們可以根據 Value 使用 switch 和 case 來執行不同的邏輯。 但是,開關和外殼提供的功能非常薄弱,只能平分秋色。 V**R 提供了乙個模式匹配 API,可以在各種情況下匹配和執行邏輯。 在清單 12 中,我們將 j**a 中的開關和 case 替換為 v**r 中的匹配和大小寫。 match 的引數是需要匹配的值。 case 的第乙個引數是匹配條件,用謂語表示; 第二個引數是滿足匹配項時的值。 $(value)
表示值的相等匹配
它表示預設匹配,相當於交換機中的預設值。
清單 12模式匹配示例。
string input = "g";string result = match(input).of( case($("g"), "good"), case($("b"), "bad"), case($("unknown"));system.out.println(result);輸出良好
在清單 13 中,我們使用:$(v ->v > 0)
將建立乙個值大於 0 的謂詞。 在這裡,匹配的結果不是特定值,而是傳遞run
製作方法 ***
清單 13使用模式匹配產生***
int value = -1;match(value).of( case($(v ->v > 0), o ->run(()system.out.println("> 0")))case($(0), o ->run(()system.out.println("0")))case($(o ->run(()system.out.println("< 0"輸出< 0
當 J**a 平台上需要複雜的函式式程式設計時,J**a 標準庫提供的支援已不再足夠。 v**r 作為 J**a 平台上流行的函式式程式設計庫,可以滿足不同的需求。 本文詳細介紹了 v**r 提供的元組、函式、值、資料結構和模式匹配。 下一篇文章將介紹單子在函式式程式設計中的重要概念。