純Java中的函數式編程:Functor和Monad示例

2020-12-04 Java從零開始

絕大多數程式設計師,特別是那些沒有功能編程背景的程式設計師,都傾向於認為monad是某種神秘的計算機科學概念,因此從理論上講,它對他們的編程事業沒有幫助。這種消極的觀點可以歸因於數十篇文章或博客文章過於抽象或過於狹窄。但是事實證明,即使在標準Java庫中,monad也無處不在,尤其是從Java Development Kit(JDK)8開始(以後會有更多介紹)。絕對妙不可言的是,一旦您第一次了解monad,突然之間就會有幾個完全不相同的目的無關的類和抽象變得熟悉。

Monad概括了各種看似獨立的概念,因此學習Monad的另一種化身只需很少的時間。例如,您不必學習CompletableFuture在Java 8中的工作方式-一旦意識到它是monad,就可以精確地知道它的工作方式,以及從其語義中可以期待什麼。然後您會聽說RxJava聽起來有很大不同,但是由於Observable是monad,因此沒有太多要添加的內容。您已經不知不覺中已經遇到過許多其他的Monads示例。因此,即使您實際上沒有使用RxJava,本節也將是有用的複習。

Functors

在解釋什麼是monad之前,讓我們研究一個稱為functor的簡單結構。Functors是封裝某些值的類型化數據結構。從語法的角度來看,Functors是具有以下API的容器:

import java.util.function.Function;

interface Functor<T> {

<R> Functor<R> map(Function<T, R> f);

}

但是僅語法是不足以了解什麼是Functors。functor提供的唯一操作是帶有函數f的map()。此函數接收框內的任何內容,對其進行轉換並將結果按原樣包裝到另一個Functors中。請仔細閱讀。Functor <T>始終是一個不可變的容器,因此map不會使執行該操作的原始對象發生突變。相反,它將返回包裝在全新Functors中的結果(或結果-請耐心等待),Functors可能是類型R。另外,Functors在應用標識函數(即map(x-> x))時不應執行任何操作。這種模式應始終返回相同的Functors或相等的實例。

通常將Functor <T>與保存T的實例進行比較,其中與該值交互的唯一方法是對其進行轉換。但是,沒有從Functors解開或逃逸的慣用方法。值始終位於Functors的上下文內。Functors為什麼有用?它們使用一個統一的,適用於所有集合的API概括了集合,promise,Optionals等多個常見習語。讓我介紹幾個Functors,以使您更流暢地使用此API:

interface Functor<T,F extends Functor<?,?>> {

<R> F map(Function<T,R> f);

}

class Identity<T> implements Functor<T,Identity<?>> {

private final T value;

Identity(T value) { this.value = value; }

public <R> Identity<R> map(Function<T,R> f) {

final R result = f.apply(value);

return new Identity<>(result);

}

}

需要額外的F類型參數來進行Identity編譯。在前面的示例中,您看到的是最簡單的Functors,僅包含一個值。您只能在map方法內部對其進行轉換,但是無法提取它。這被認為超出了純Functors的範圍。與Functors進行交互的唯一方法是應用類型安全的轉換序列:

Identity<String> idString = new Identity<>("abc");

Identity<Integer> idInt = idString.map(String::length);

或流利地,就像您編寫函數一樣:

Identity<byte[]> idBytes = new Identity<>(customer)

.map(Customer::getAddress)

.map(Address::street)

.map((String s) -> s.substring(0, 3))

.map(String::toLowerCase)

.map(String::getBytes);

從這個角度來看,在Functors上的映射與調用鏈式函數沒有太大不同:

byte[] bytes = customer

.getAddress()

.street()

.substring(0, 3)

.toLowerCase()

.getBytes();

您為什麼還要打擾這樣冗長的包裝,不僅不提供任何附加值,而且也不能將內容提取回去?好了,事實證明您可以使用此原始Functors抽象對其他幾個概念建模。例如,從Java 8開始,可選的是帶有map()方法的Functors。讓我們從頭開始實現它:

class FOptional<T> implements Functor<T,FOptional<?>> {

private final T valueOrNull;

private FOptional(T valueOrNull) {

this.valueOrNull = valueOrNull;

}

public <R> FOptional<R> map(Function<T,R> f) {

if (valueOrNull == null)

return empty();

else

return of(f.apply(valueOrNull));

}

public static <T> FOptional<T> of(T a) {

return new FOptional<T>(a);

}

public static <T> FOptional<T> empty() {

return new FOptional<T>(null);

}

}

現在變得有趣了。一個FOptional<T>仿函數可以持有價值,但同樣也可能是空的。這是一種類型安全的編碼方式null。有兩種構造方法FOptional-通過提供值或創建 empty()實例。在這兩種情況下,就像with一樣Identity,FOptional都是不可變的,我們只能與內部的值進行交互。不同之處FOptional在於,如果轉換函數f為空,則可能不會將其應用於任何值。這意味著Functors可能未必必須完全封裝type的一個值T。它也可以包裝任意數量的值,就像List... functor:

import com.google.common.collect.ImmutableList;

class FList<T> implements Functor<T, FList<?>> {

private final ImmutableList<T> list;

FList(Iterable<T> value) {

this.list = ImmutableList.copyOf(value);

}

@Override

public <R> FList<?> map(Function<T, R> f) {

ArrayList<R> result = new ArrayList<R>(list.size());

for (T t : list) {

result.add(f.apply(t));

}

return new FList<>(result);

}

}

API保持不變:您可以在轉換中使用Functors-但行為卻大不相同。現在,我們對FList中的每個項目進行轉換,以聲明方式轉換整個列表。因此,如果您有客戶列表,並且想要他們的街道列表,則非常簡單:

import static java.util.Arrays.asList;

FList<Customer> customers = new FList<>(asList(cust1, cust2));

FList<String> streets = customers

.map(Customer::getAddress)

.map(Address::street);

這不再像說那麼簡單customers.getAddress().street(),您不能getAddress()在一個客戶集合上調用,您必須getAddress()在每個單獨的客戶上調用,然後將其放回一個集合中。順便說一句,Groovy發現這種模式是如此普遍,以至於實際上它有一個語法糖:customer*.getAddress()*.street()。該運算符稱為散點,實際上是一種map偽裝。也許您想知道為什麼我要在list內部手動迭代map而不是使用StreamJava 8中的s list.stream().map(f).collect(toList())?這會響嗎?如果我java.util.stream.Stream<T>用Java 告訴您也是Functors怎麼辦?順便說一句,一個Monads?

現在,您應該看到Functors的第一個好處-它們抽象了內部表示形式,並為各種數據結構提供了一致且易於使用的API。作為最後一個示例,讓我介紹類似於的 promise函數Future。Promise「承諾」有一天將提供一個值。它尚未出現,可能是因為產生了一些後臺計算,或者我們正在等待外部事件。但是它將在將來的某個時間出現。完成a Promise<T>的機制並不有趣,但是Functors的性質是:

Promise<Customer> customer = //...

Promise<byte[]> bytes = customer

.map(Customer::getAddress)

.map(Address::street)

.map((String s) -> s.substring(0, 3))

.map(String::toLowerCase)

.map(String::getBytes);

看起來很熟悉?這就是我想說的!

Functors的實現超出了本文的範圍,甚至不重要。不用說,我們非常接近從Java 8實現CompletableFuture,並且幾乎從RxJava中發現了Observable。但是回到Functors。Promise <客戶>尚未持有客戶的值。它有望在將來具有這種價值。但是,我們仍然可以像使用FOptional和FList一樣映射此類Functors-語法和語義完全相同。行為遵循Functors表示的內容。調用customer.map(Customer :: getAddress)會產生Promise <Address>,這意味著地圖是非阻塞的。customer.map()將客戶承諾完成。相反,它將返回另一個不同類型的promise。當上遊承諾完成後,下遊承諾應用傳遞給map()的函數並將結果傳遞給下遊。突然,我們的Functors使我們能夠以非阻塞方式流水線進行異步計算。但是您不必了解或學習-因為Promise是Functors,所以它必須遵循語法和法則。

Functors還有許多其他很好的例子,例如以組合方式表示值或錯誤。但是現在是時候看看Monads了。

從 Functors到Monads

我假設您了解Functors是如何工作的,為什麼它們是有用的抽象。但是Functors並不像人們期望的那樣普遍。如果您的轉換函數(作為map()的一個參數傳遞)返回Functors實例而不是簡單值,會發生什麼情況?好吧,Functors也是一個值,因此不會發生任何不良情況。將返回的所有內容放回Functors中,以便所有行為都保持一致。但是,假設您有以下方便的方法來解析字符串:

FOptional<Integer> tryParse(String s) {

try {

final int i = Integer.parseInt(s);

return FOptional.of(i);

} catch (NumberFormatException e) {

return FOptional.empty();

}

}

例外是會影響類型系統和功能純度的副作用。在純函數式語言中,沒有例外的地方。畢竟,我們從未聽說過在數學課上拋出異常,對嗎?錯誤和非法條件使用值和包裝器明確表示。例如,tryParse()接受一個String,而不是簡單地返回一個int或在運行時靜默引發異常。通過類型系統,我們明確地告訴了tryParse()可能失敗,字符串格式錯誤沒有任何異常或錯誤。此半故障由可選結果表示。有趣的是,Java已經檢查了必須聲明和處理的異常,因此從某種意義上講,Java在這方面更純淨,它沒有隱藏副作用。但是對於Java中通常不建議檢查的異常情況,因此,讓我們回到tryParse()。用已經包裝在FOptional中的String組成tryParse似乎很有用:

FOptional<String> str = FOptional.of("42");

FOptional<FOptional<Integer>> num = str.map(this::tryParse);

這不足為奇。如果tryParse()返回a,int您將得到FOptional<Integer> num,但是由於map()函數FOptional<Integer>本身返回,因此將其包裝兩次成尷尬FOptional<FOptional<Integer>>。請仔細查看類型,您必須了解為什麼我們在這裡得到這種雙重包裝。除了看上去很恐怖之外,在Functors中放一個Functors會破壞構圖和流暢的連結:

FOptional<Integer> num1 = //...

FOptional<FOptional<Integer>> num2 = //...

FOptional<Date> date1 = num1.map(t -> new Date(t));

//doesn't compile!

FOptional<Date> date2 = num2.map(t -> new Date(t));

在這裡,我們嘗試FOptional通過轉換int為+ Date + 映射內容。有了int -> Date我們可以輕鬆地從轉換Functor<Integer>為的功能Functor<Date>,我們知道它是如何工作的。但是在 num2 情況變得複雜的情況下。什麼num2.map()接收輸入的不再是一個int,但一個FOoption<Integer>顯然java.util.Date不具備這樣的構造。我們通過雙重包裹打破了Functors。但是,擁有返回Functors而不是簡單值的函數非常普遍(如tryParse()),我們不能簡單地忽略這種要求。一種方法是引入一種特殊的無參數join()方法,以「展平」嵌套Functors:

FOptional<Integer> num3 = num2.join()

它可以工作,但是因為這種模式太普遍了,所以flatMap()引入了名為的特殊方法。flatMap()與以下內容非常相似,map但希望作為參數接收的函數返回Functors-或準確地說是monad:

interface Monad<T,M extends Monad<?,?>> extends Functor<T,M> {

M flatMap(Function<T,M> f);

}

我們簡單地得出結論,這flatMap只是一種語法糖,可以使成分更好。但是flatMap方法(通常稱為Haskell bind或>>=從Haskell 調用)具有所有不同,因為它允許以純淨的功能樣式構成複雜的轉換。如果FOptional是monad的實例,則解析突然可以按預期進行:

FOptional<String> num = FOptional.of("42");

FOptional<Integer> answer = num.flatMap(this::tryParse);

Monads不需要實現map,它可以flatMap()很容易地實現。事實上flatMap,必不可少的運算符可實現全新的轉換領域。顯然,就像Functors一樣,句法順從性不足以將某類稱為Monads,flatMap()操作員必須遵守Monads法則,但是它們非常直觀,就像flatMap()與身份的結合一樣。後者要求m(x).flatMap(f)與f(x)持有值x和函數的任何monad 相同f。我們不會深入研究monad理論,而讓我們關注實際含義。例如,當單聲道內部結構不重要時,它們會發光Promise未來將具有價值的monad。您可以從類型系統中猜出Promise在以下程序中將如何運行嗎?首先,所有可能花費一些時間才能完成的方法都返回a Promise:

import java.time.DayOfWeek;

Promise<Customer> loadCustomer(int id) {

//...

}

Promise<Basket> readBasket(Customer customer) {

//...

}

Promise<BigDecimal> calculateDiscount(Basket basket, DayOfWeek dow) {

//...

}

現在,我們可以像使用monadic運算符一樣阻止所有這些函數的方式編寫這些函數:

Promise<BigDecimal> discount =

loadCustomer(42)

.flatMap(this::readBasket)

.flatMap(b -> calculateDiscount(b, DayOfWeek.FRIDAY));

這變得很有趣。flatMap()必須保留Monads類型,因此所有中間對象均為Promises。這不僅僅是保持類型有序-前一個程序突然完全異步!loadCustomer()返回一個,Promise因此它不會阻塞。readBasket()接受Promise具有(將具有)的任何東西,並應用返回另一個函數的函數Promise,依此類推。基本上,我們建立了一個異步計算管道,其中後臺完成一個步驟會自動觸發下一步。

探索 flatMap()

有兩個Monads並將它們包含的值組合在一起是很常見的。但是,Functors和monad都不允許直接訪問其內部,這是不純的。相反,我們必須謹慎地應用轉換,而不能逃脫monad。假設您有兩個Monads,並且想要將它們合併:

import java.time.LocalDate;

import java.time.Month;

Monad<Month> month = //...

Monad<Integer> dayOfMonth = //...

Monad<LocalDate> date = month.flatMap((Month m) ->

dayOfMonth

.map((int d) -> LocalDate.of(2016, m, d)));

請花點時間研究前面的偽代碼。我不使用任何真正的monad實現方式,Promise也不List強調核心概念。我們有兩個獨立的Monads,一個是type Month,另一個是type Integer。為了構建LocalDate它們,我們必須構建一個嵌套的轉換,該轉換可以訪問兩個monad的內部。仔細研究這些類型,尤其要確保您了解為什麼我們flatMap在一個地方和另一個地方使用map()。想想如果您也有三分之一的話,將如何構造該代碼Monad<Year>。應用的兩個參數的函數(的這種模式m,並d在我們的例子)是很常見的,在Haskell有一個名為特殊輔助函數liftM2正是在map和之上實現的轉換flatMap。在Java偽語法中,它看起來像這樣:

Monad<R> liftM2(Monad<T1> t1, Monad<T2> t2, BiFunction<T1, T2, R> fun) {

return t1.flatMap((T1 tv1) ->

t2.map((T2 tv2) -> fun.apply(tv1, tv2))

);

}

您不必為每個monad都實現此方法,這flatMap()已經足夠了,而且,它對所有monad都一致地起作用。liftM2當您考慮如何將其與各種monad結合使用時,它非常有用。例如,listM2(list1, list2, function)將應用於和(笛卡爾積)function上的所有可能的項目對。另一方面,對於可選選項,僅當兩個可選選項均為非空時,它將應用功能。更好的是,對於 monad,當兩個都完成時,函數將異步執行。這意味著我們只是發明了一個簡單的同步機制(在fork-join算法中),該機制包含兩個異步步驟。list1list2Promise Promisejoin()

我們可以輕鬆構建的另一個有用的運算符flatMap()是filter(Predicate<T>),它接受monad中的所有內容,如果不符合某些謂詞,則將其完全丟棄。在某種程度上,它類似於map1-to-1映射,而不是1-to-1映射。同樣filter(),每個monad具有相同的語義,但取決於我們實際使用的monad,其功能卻非常出色。顯然,它允許從列表中過濾掉某些元素:

FList<Customer> vips =

customers.filter(c -> c.totalOrders > 1_000);

但是它也可以很好地工作,例如對於可選項目。在這種情況下,如果可選內容不符合某些條件,我們可以將非空可選轉換為空。空的可選部分保持不變。

從Monads列表到Monads列表

源自flatMap()的另一個有用的運算符是sequence()。您只需查看類型籤名即可輕鬆猜測其作用:

Monad<Iterable<T>> sequence(Iterable<Monad<T>> monads)

通常,我們有一堆相同類型的monad,而我們想要一個具有該類型列表的monad。這對您來說可能聽起來很抽象,但卻非常有用。想像一下,您想通過ID同時從資料庫中加載一些客戶,因此您loadCustomer(id)多次對不同的ID 使用方法,每次調用都返回Promise<Customer>。現在,您有一個的列表,Promise但您真正想要的是一個客戶列表,例如要在Web瀏覽器中顯示的客戶列表。將 sequence()(在RxJava sequence()被稱為concat()或merge()根據使用情況)運算符剛建成為:

FList<Promise<Customer>> custPromises = FList

.of(1, 2, 3)

.map(database::loadCustomer);

Promise<FList<Customer>> customers = custPromises.sequence();

customers.map((FList<Customer> c) -> ...);

通過調用每個ID,FList<Integer>我們擁有一個具有代表性的客戶ID map(您知道它對FList仿函數有何幫助?)database.loadCustomer(id)。這導致Promises的列表非常不便。sequence()節省了一天的時間,但這再次不僅僅是語法糖。前面的代碼是完全非阻塞的。對於不同種類的Monadssequence()還是有意義的,但是在不同的計算環境中。例如,它可以更改FList<FOptional<T>>為FOptional<FList<T>>。順便說一句,您可以在之上實現sequence()(就像map())flatMap()。

flatMap()一般而言,這只是冰山一角。儘管源於晦澀的類別理論,但即使在Java等面向對象的程式語言中,monad也被證明是極其有用的抽象。能夠組成返回Monads函數的函數非常有用,以至於數十個無關的類遵循Monads行為。

而且,一旦將數據封裝在monad中,通常很難顯式地將其取出。這種操作不是monad行為的一部分,並且經常導致非慣用語代碼。例如,Promise.get()on Promise<T>可以從技術上返回T,但只能通過阻塞返回,而所有基於的運算符flatMap()都是非阻塞的。另一個示例是FOptional.get(),但是可能失敗,因為FOptional可能為空。即使FList.get(idx)從列表中偷看特定元素也聽起來很尷尬,因為您可以經常替換for循環map()。

我希望您現在了解為什麼現在這些Monads如此流行。即使在像Java這樣的面向對象的語言中,它們也是非常有用的抽象。

最後,開發這麼多年我也總結了一套學習Java的資料與面試題,如果你在技術上面想提升自己的話,可以關注我,私信發送領取資料或者在評論區留下自己的聯繫方式,有時間記得幫我點下轉發讓跟多的人看到哦。

相關焦點

  • 給JavaScript 開發者講講函數式編程 - OSCHINA - 中文開源技術...
    ,我在幾個月前聽到了很多關於函數式編程的東西,不過並沒有更深入的了解。從那時起,我開始更深地了解函數式編程並且我覺得應該為那些總能聽到它但不知道究竟是什麼的新人做一點事情。這就使得在編譯時很難去靜態分析和優化程序。不過對 JavaScript 開發者來說更加有用的是,純函數降低了程序的認知難度。寫純函數時,你僅僅需要關注函數體本身。不必去擔心一些外部因素所帶來的問題,比如在 addx 函數中的 x 被改變。
  • 組合軟體:為什麼要在 JavaScript 中學習函數式編程?
    最後從零開始探索 ES6 和 函數式編程!希望所有新概念在這個過程中都被解釋到了 - 但是別指望會太舒適。如果你是一個已經熟悉 JavaScript 或者純函數式語言的老手,也許你會認為用 JavaScript 來探究函數式編程簡直個笑話。請把這些想法放一邊,試著用開放的心態來接觸這份資料。
  • javascript函數式編程基礎指北
    JavaScript 作為一種典型的多範式程式語言,這兩年隨著React\vue的火熱,函數式編程的概念也開始流行起來,lodashJS、folktale等多種開源庫都使用了函數式的特性。程序的本質是:根據輸入通過某種運算得到輸出函數式編程(Functional programming)是一種編程思想/範式 ,其核心思想是將運算過程抽象成函數(指數學的函數而不是程序的方法或函數即純函數),也就是面向函數編程,描述函數/數據 之間的映射,做到最大程度的復用; 學習函數式編程真正的意義在於讓你認識到一種用函數的角度去抽象問題的思路
  • 為什麼函數式編程在Java中很危險?
    在我的日常工作中,我身邊的開發者大多是畢業於CS編程頂級院校比如MIT、CMU以及Chicago,他們初次涉及的語言是Haskell、Scheme及Lisp。他們認為函數式編程是一種自然的、直觀的、美麗的且高效的編程樣式。
  • Java中的函數式接口
    什麼是函數式接口java中函數式接口中的定義如下:函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。需要注意以下幾點:JDK1.8中接口支持默認方法,默認方法和靜態方法都不屬於抽象方法,因此,函數式接口可以包含默認方法和抽象方法;接口默認繼承java.lang.Object,如果接口顯示聲明覆蓋了Object中方法,也不算抽象方法,例如,toString(),equals()等。
  • 面試被虐題:說說 JVM 系語言的函數式編程
    一些基本特點總結:相比平常的指令式編程,函數式編程更在乎執行結果而非過程;函數是一等公民,可以像普通的數值、引用等變量一樣賦值、作為參數傳遞、作為返回值;函數是純函數,即函數不能產生副作用,如不能修改全局變量等,固定的輸入就映射固定的輸出。
  • 這樣理解 Java 中的函數式編程就對了
    在眾多的編程範式中,大多數開發人員比較熟悉的是面向對象編程範式。一方面是由於面向對象程式語言比較流行,與之相關的資源比較豐富,比如Java,c++等。另外一方面是由於大部分學校和培訓機構的課程設置,都選擇流行的面向對象程式語言。面向對象編程範式的優點在於其抽象方式與現實中的概念比較相近。比如,學生、課程、汽車和訂單等這些現實中的概念,在抽象成相應的類之後,我們很容易就能理解類之間的關聯關係。
  • 深入理解Java中的Lambda表達式和函數式編程的關係
    上一篇文章(https://www.toutiao.com/i6824085046475883022/),我們理解了函數式編程基本思想、概念和Java對函數式編程的基本使用。本篇我們來聊聊lambda表達式和函數式編程的關係。當提到 Java 8 的時候,Lambda 表達式總是第一個提到的新特性。
  • 函數式編程簡介—Lambda 表達式
    Java作為面向對象的程式語言,如果按照編程種類劃分屬於命令式編程(Imperative Programming)。常見的編程範式還有邏輯式編程(Logic Programming),函數式編程(Functional Programming)。
  • 程式設計師為何與函數式編程「墜入愛河」?
    現在,當查看函數聲明時,用戶能確切地知道發生了什麼。因此,如果程序運行不正常,用戶也可以輕而易舉地單獨測試每個功能,並查明哪個功能有問題函數式編程正在編寫純函數沒有副作用的函數是指其輸入和輸出都具有明確的聲明,而沒有副作用的功能就是純函數。
  • Java新特性之-函數式編程
    在眾多的編程範式中,大多數開發人員比較熟悉的是面向對象編程範式。一方面是由於面向對象程式語言比較流行,與之相關的資源比較豐富,比如Java,c++等。另外一方面是由於大部分學校和培訓機構的課程設置,都選擇流行的面向對象程式語言。
  • 大數據中java與mapreduce編程
    優點:容錯性強易於編程擴展伸縮性強可處理海量數據缺點:不擅長實時和流式計算不擅長有向無環圖DAG計算無法高效存儲大量小文件用於一次寫多次讀場景,不支持多用戶寫入和隨機文件修改mapreduce運行過程運行過程涉及input、split
  • 大數據中java與mapreduce編程
    優點:容錯性強易於編程擴展伸縮性強可處理海量數據缺點: 不擅長實時和流式計算不擅長有向無環圖DAG計算無法高效存儲大量小文件用於一次寫多次讀場景,不支持多用戶寫入和隨機文件修改>hive中UDF示例java MR編程用java
  • 從Python到Haskell:程式設計師為何與函數式編程「墜入愛河」?
    現在,當查看函數聲明時,用戶能確切地知道發生了什麼。因此,如果程序運行不正常,用戶也可以輕而易舉地單獨測試每個功能,並查明哪個功能有問題函數式編程正在編寫純函數沒有副作用的函數是指其輸入和輸出都具有明確的聲明,而沒有副作用的功能就是純函數。函數式編程一個非常簡單的定義:僅用純函數編寫程序。純函數永遠不會修改變量,而只會創建新的變量作為輸出。
  • Java如何支持函數式編程?
    本文通過代碼實例,從Stream類、Lambda表達式和函數接口這三個語法概念來分享Java對函數式編程的支持。函數式編程因其編程的特殊性,僅在科學計算、數據處理、統計分析等領域,才能更好地發揮它的優勢,所以它並不能完全替代更加通用的面向對象編程範式。但是作為一種補充,它也有很大存在、發展和學習的意義。
  • 函數式編程是未來的最佳編碼範例嗎?
    簡而言之,函數式編程需要為固定變量構建純函數並通過其響應更改狀態。 相反,其他程式語言具有通過更改程序中變量引用來更改應用程式狀態的能力。 您可以在本文中了解功能編程與面向對象程序的更多區別。由於其純粹的性質,函數式編程對於令人垂涎的任務(如機器學習和數據分析)印象深刻。
  • Java之Lambda函數式編程應用舉例,鏈式語法「真乾貨來拿走」
    Java之Lambda函數式編程應用舉例,鏈式語法「真乾貨來拿走」 java 8 Lambda函數式編程,像阿里、騰訊這樣的大網際網路公司早就已經使用的技術。學習下jdk8的新特性,對提高開發效率和寫出缺陷更低的代碼都非常有好處,時代在進步程式語言也在進化。
  • 函數式編程那些事兒
    函數式編程是一種編程範式,在其中它試圖將每個函數都綁定到純數學函數中。這是一種聲明式的編程風格,著重於解決什麼而不是如何解決。Clojure,Common Lisp,Erlang,Haskell和Scala是遵循函數式編程方法的一些著名程式語言。編程範例基於lambda演算,下面簡要說明:Lambda演算它使用表達式來代替語句。
  • Java函數式編程快速入門:Lambda表達式與Stream API
    下面的例子中,我們自己設計一個加法的函數式接口AddInterface,然後實現這個接口。關於interface、<T>泛型等知識,可以參考我前兩篇文章:和。,我們知道在實現一個Lambda表達式時,Lambda表達式實際上是在實現這個函數式接口中的虛函數,Lambda表達式的輸入類型和返回類型要與虛函數定義的類型相匹配。