使用 Vavr 進行函式式程式設計

Mondo 科技 更新 2024-02-16

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 次curriedapply呼叫,將值應用於所有 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 中,使用了原始函式實現bigintegerpow計算功率的方法。 用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.someoption.none,分別對應兩種情況,有值和無值。 用option.some可以建立包含給定值的方法some物件,而option.none是的none物件的例項。 option還支援常用的mapflatmapfilter依此類推,如清單 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物件。 eithermapmapleft右值和左值是分開計算的。

清單 7使用兩者之一的示例。

import io.v**r.control.either;import j**a.util.concurrent.threadlocalrandom;public class eithers private static either compute()
try用於表示可能產生異常的計算。 try該介面有兩個實現類try.successtry.failure,分別代表成功和失敗。 try.success在計算成功時封裝返回值,而try.failure然後封裝計算失敗throwable物件。 可以從介面獲取 try 的例項checkedfunction0callablerunnablesuppliertry還提供mapfilter等方法。 值得一提的是tryrecover在發生錯誤時從異常中恢復的方法。

在清單 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從站介面supplierlazy物件。 方法: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 的第乙個流的元素滿足謂詞指定的條件,第二個流的元素不滿足謂詞指定的條件。 scanleftscanright:分別按從左到右或從右到左的順序對元素呼叫函式,並累加結果。 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 提供的元組、函式、值、資料結構和模式匹配。 下一篇文章將介紹單子在函式式程式設計中的重要概念。

相關問題答案

    如何使用excel中的rank函式進行排名,解釋rank函式引數和公式的使用

    Excel 中的 rank 函式專用於資料排名場景,在本節中,我們將詳細了解 rank 函式的完整三個引數及其公式應用。我們來看乙個例子,資料表包含產品和銷售額,現在我們想按照銷售額進行排名,排名方法從大到小依次降序,即最大值排,最小值排在最後。如何使用rank函式來表達排名公式,我們先看一下它的語...

    如何使用工作日功能

    weekday該函式用於返回指定日期所在的星期幾,是Excel中常用的日期和時間函式。在這裡 weekday功能基本用法 weekday serial number,return type serial number 必需,表示要確定日期的星期幾。它可以是包含日期的單元格引用,也可以是直接輸入的日期...

    如何使用 INDEX 函式

    索引函式介紹 索引函式是Excel中常用的搜尋函式之一,用於返回表或區域中的值或對值的引用,並用於查詢指定區域中行或列中的資料。函式一般有兩種形式 陣列 通常返回乙個數值或乙個數值陣列引文 通常返回引用。.索引函式的句法結構 index array,row num,column num .第乙個引數...

    如何使用間接函式

    間接函式是在電子軟體 如 Microsoft Excel 中用於引用其他單元格內容的函式。它的主要作用是通過給定的單元格引用獲取單元格的內容,而無需直接使用單元格引用。以下是使用間接函式的方法 .開啟 Excel,建立新工作簿或開啟現有工作簿。.在乙個單元格中輸入間接函式。例如,在單元格 A 中輸入...

    如何使用間接函式

    間接函式概述 在數學和工程領域,間接函式是乙個重要的工具,它使我們能夠描述乙個函式如何被另乙個函式轉換。間接函式的應用範圍很廣,如物理 化學 生物學 金融等領域。了解如何使用間接函式可以幫助我們更好地理解和解決各種問題。.間接功能的定義 間接函式,也稱為隱式函式,是一種不能簡單地用代數表示的函式。它...