真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

功能風(fēng)格:Lambda函數(shù)和地圖

一級(jí)函數(shù):Lambda函數(shù)和Map

什么是一級(jí)函數(shù)?

你可能聽(tīng)過(guò)它之前說(shuō)過(guò),一種特定的語(yǔ)言是功能性的,因?yàn)樗小耙涣鞯墓δ堋?。正如我在本系列關(guān)于函數(shù)式編程的第一篇文章中所說(shuō)的那樣,我不贊同這種流行的觀點(diǎn)。我同意一流的函數(shù)是任何函數(shù)式語(yǔ)言的基本特征,但我不認(rèn)為這是語(yǔ)言具有功能性的充分條件。有許多命令式語(yǔ)言也具有這一特性。但是,什么是一流的函數(shù)呢?功能描述為頭等艙當(dāng)它們可以像任何其他值一樣處理時(shí)-也就是說(shuō),它們可以在運(yùn)行時(shí)被動(dòng)態(tài)地分配給一個(gè)名稱或符號(hào)。它們可以存儲(chǔ)在數(shù)據(jù)結(jié)構(gòu)中,通過(guò)函數(shù)參數(shù)傳入,并作為函數(shù)返回值返回。

作為一家“創(chuàng)意+整合+營(yíng)銷”的成都網(wǎng)站建設(shè)機(jī)構(gòu),我們?cè)跇I(yè)內(nèi)良好的客戶口碑。創(chuàng)新互聯(lián)公司提供從前期的網(wǎng)站品牌分析策劃、網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、成都網(wǎng)站建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、創(chuàng)意表現(xiàn)、網(wǎng)頁(yè)制作、系統(tǒng)開(kāi)發(fā)以及后續(xù)網(wǎng)站營(yíng)銷運(yùn)營(yíng)等一系列服務(wù),幫助企業(yè)打造創(chuàng)新的互聯(lián)網(wǎng)品牌經(jīng)營(yíng)模式與有效的網(wǎng)絡(luò)營(yíng)銷方法,創(chuàng)造更大的價(jià)值。

這其實(shí)不是一個(gè)新奇的想法。函數(shù)指針從1972年開(kāi)始就一直是C的一個(gè)特性。在此之前,過(guò)程引用是Algol 68的一個(gè)特性,于1970年實(shí)現(xiàn),當(dāng)時(shí),它們被認(rèn)為是程序性編程特性追溯到更久以前,Lisp(首次實(shí)現(xiàn)于1963年)是建立在程序代碼和數(shù)據(jù)是可互換的概念之上的。

這些也不是模糊的特性。在C語(yǔ)言中,我們通常使用函數(shù)作為一流的對(duì)象。例如,在排序時(shí):

char **array = randomStrings();

printf("Before sorting:\n");
for (int s = 0; s < NO_OF_STRINGS; s++)
    printf("%s\n", array[s]);

qsort(array, NO_OF_STRINGS, sizeof(char *), compare);

printf("After sorting:\n");
for (int s = 0; s < NO_OF_STRINGS; s++)
    printf("%s\n", array[s]);

這,這個(gè),那,那個(gè)stdlibC中的庫(kù)為不同類型的排序例程提供了一組函數(shù)。它們都能夠?qū)θ魏晤愋偷臄?shù)據(jù)進(jìn)行排序:程序員所需要的唯一幫助就是提供一個(gè)比較數(shù)據(jù)集的兩個(gè)元素并返回的函數(shù)。-11,或0,指示哪個(gè)元素大于另一個(gè)元素或它們相等。

這本質(zhì)上就是戰(zhàn)略模式!

指向字符串的指針數(shù)組的比較器函數(shù)可以是:

int compare(const void *a, const void *b)
{
    char *str_a = *(char **) a;
    char *str_b = *(char **) b;
    return strcmp(str_a, str_b);
}

然后,我們將其傳遞給排序函數(shù),如下所示:

qsort(array, NO_OF_STRINGS, sizeof(char *), compare);

控件上沒(méi)有括號(hào)。compare函數(shù)名使編譯器發(fā)出函數(shù)指針,而不是函數(shù)調(diào)用。因此,在C中將函數(shù)視為頭等對(duì)象是非常容易的,盡管接受函數(shù)指針的函數(shù)的簽名非常難看:

qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *));

函數(shù)指針不僅用于排序。早在.NET發(fā)明之前,就有用于編寫(xiě)MicrosoftWindows應(yīng)用程序的Win 32 API。在此之前,有Win16API。它自由地使用函數(shù)指針作為回調(diào)。應(yīng)用程序在調(diào)用窗口管理器時(shí)提供了指向其自身函數(shù)的指針,當(dāng)應(yīng)用程序需要通知某個(gè)已經(jīng)發(fā)生的事件時(shí),窗口管理器將調(diào)用該窗口管理器。您可以認(rèn)為這是應(yīng)用程序(觀察者)與其窗口(可觀察的)之間的一個(gè)觀察者模式關(guān)系-應(yīng)用程序接收到了發(fā)生在其窗口上的事件的通知,例如鼠標(biāo)單擊和按鍵盤。在窗口管理器中抽象了管理窗口的工作-移動(dòng)窗口,將它們堆疊在一起,決定哪個(gè)應(yīng)用程序是用戶操作的接收者。這些應(yīng)用程序?qū)λ鼈児蚕憝h(huán)境的其他應(yīng)用程序一無(wú)所知。在面向?qū)ο蟮木幊讨?,我們通常通過(guò)抽象類和接口來(lái)實(shí)現(xiàn)這種解耦,但也可以使用一流的函數(shù)來(lái)實(shí)現(xiàn)。

所以,我們使用一流的函數(shù)已經(jīng)有很長(zhǎng)時(shí)間了。但是,公平地說(shuō),沒(méi)有一種語(yǔ)言比簡(jiǎn)陋的Javascript更能廣泛地推廣作為一流公民的功能。

Lambda表達(dá)式

在Javascript中,將函數(shù)傳遞給用作回調(diào)的其他函數(shù)一直是一種標(biāo)準(zhǔn)做法,就像在Win 32 API中一樣。這個(gè)想法是HTML DOM的一個(gè)組成部分,其中第一類函數(shù)可以作為事件偵聽(tīng)器添加到DOM元素中:

function myEventListener() {
    alert("I was clicked!")
}
...
var myBtn = document.getElementById("myBtn")
myBtn.addEventListener("click", myEventListener)

就像在C中一樣,myEventListener函數(shù)名時(shí),在調(diào)用addEventListener意味著它不會(huì)立即執(zhí)行。相反,該函數(shù)與click事件中的DOM元素。當(dāng)單擊元素時(shí),然后將調(diào)用該函數(shù)并發(fā)生警報(bào)。

流行的jQuery庫(kù)通過(guò)證明通過(guò)查詢字符串選擇DOM元素的函數(shù)簡(jiǎn)化了流程,并提供了操作元素和向元素添加事件偵聽(tīng)器的有用函數(shù):

$("#myBtn").click(function() {
    alert("I was clicked!")
})

類中使用的第一類函數(shù)也是實(shí)現(xiàn)異步I/O的方法。XMLHttpRequest對(duì)象,它是Ajax的基礎(chǔ)。同樣的想法在Node.js中也很普遍。當(dāng)您想要進(jìn)行一個(gè)非阻塞函數(shù)調(diào)用時(shí),將它傳遞給一個(gè)函數(shù)的引用,以便在它完成時(shí)調(diào)用您。

但是,這里還有別的東西。第二個(gè)例子不僅僅是一個(gè)一流函數(shù)的例子。它也是Lambda函數(shù)。具體而言,本部分:

function() {
    alert("I was clicked!");
}

lambda函數(shù)(通常被稱為蘭卜達(dá))是一個(gè)未命名的函數(shù)。他們本可以叫他們匿名函數(shù),這樣每個(gè)人都會(huì)立刻知道他們是什么。但是,這聽(tīng)起來(lái)不那么令人印象深刻,所以lambda函數(shù)就是!lambda函數(shù)的要點(diǎn)是在那里只需要一個(gè)函數(shù);因?yàn)樗谌魏蔚胤蕉疾恍枰?,所以您只需要在那里定義它。不需要名字。如果你需要在其他地方重用它,然后考慮將它定義為一個(gè)命名函數(shù),然后按名稱引用它,就像我在第一個(gè)Javascript示例中所做的那樣。如果沒(méi)有l(wèi)ambda函數(shù),使用jQuery和Node進(jìn)行編程確實(shí)會(huì)令人厭煩。

LAMBDA函數(shù)以不同的方式以不同的語(yǔ)言定義:

在Javascript中:function(a, b) { return a + b }

在Java中:(a, b) -> a + b

在C#中:(a, b) => a + b

在Clojure中:(fn [a b] (+ a b))

在Clojure中-速記版本:#(+ %1 %2)

在Groovy中:{ a, b -> a + b }

在F#中:fun a b -> a + b

在Ruby中,所謂的“穩(wěn)定”語(yǔ)法:-> (a, b) { return a + b }

正如我們所看到的,大多數(shù)語(yǔ)言都傾向于一種比Javascript更簡(jiǎn)潔的表達(dá)lambdas的方式。

地圖

您可能已經(jīng)在編程中使用了“map”一詞來(lái)表示將對(duì)象存儲(chǔ)為鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu)(如果您的語(yǔ)言稱它為“字典”,那么就沒(méi)有問(wèn)題了)。在函數(shù)式編程中,這個(gè)詞還有一個(gè)額外的含義。事實(shí)上,基本概念是一樣的。在這兩種情況下,一組事物被映射到另一組事物。在數(shù)據(jù)結(jié)構(gòu)的意義上,映射是一個(gè)名詞-鍵被映射到值。在編程意義上,map是一個(gè)動(dòng)詞-一個(gè)函數(shù)將一個(gè)值數(shù)組映射到另一個(gè)值數(shù)組。

假設(shè)你有一個(gè)功能f以及一系列的值A = [A1A2A3A4]地圖f過(guò)關(guān)A手段應(yīng)用f中的每一個(gè)元素A:

  • A1 → f (A1) = a1‘

  • A2 → f (A2) = a2‘

  • A3 → f (A3) = A3‘

  • A4 → f (A4) = A4‘

然后,按照與輸入相同的順序組裝結(jié)果數(shù)組:

A‘=地圖(fA ) = [a1‘a2‘A3‘A4‘]

逐個(gè)圖

好吧,這很有趣,但是位數(shù)學(xué)。你多久會(huì)這么做一次?實(shí)際上,這比你想象的要頻繁得多。像往常一樣,有一個(gè)例子最能解釋事情,所以讓我們看一看我從下面舉出來(lái)的一個(gè)簡(jiǎn)單的練習(xí)exercism.io當(dāng)我學(xué)習(xí)Clojure的時(shí)候。這項(xiàng)運(yùn)動(dòng)被稱為“RNA轉(zhuǎn)錄”,它非常簡(jiǎn)單。我們將看一看需要轉(zhuǎn)錄成輸出字符串的輸入字符串。這些基礎(chǔ)是這樣翻譯的:

  • C→G

  • G→C

  • →U

  • T→A

除C、G、A、T以外的任何輸入都是無(wú)效的。JUnit 5中的測(cè)試可能如下所示:

class TranscriberShould {

    @ParameterizedTest
    @CsvSource({
            "C,G",
            "G,C",
            "A,U",
            "T,A",
            "ACGTGGTCTTAA,UGCACCAGAAUU"
    })
    void transcribe_dna_to_rna(String dna, String rna) {
        var transcriber = new Transcriber();
        assertThat(transcriber.transcribe(dna), is(rna));
    }

    @Test
    void reject_invalid_bases() {
        var transcriber = new Transcriber();
        assertThrows(
                IllegalArgumentException.class,
                () -> transcriber.transcribe("XCGFGGTDTTAA"));
    }
}

而且,我們可以通過(guò)這個(gè)Java實(shí)現(xiàn)通過(guò)測(cè)試:

class Transcriber {

    private Map pairs = new HashMap<>();

    Transcriber() {
        pairs.put('C', 'G');
        pairs.put('G', 'C');
        pairs.put('A', 'U');
        pairs.put('T', 'A');
    }

    String transcribe(String dna) {
        var rna = new StringBuilder();
        for (var base: dna.toCharArray()) {
            if (pairs.containsKey(base)) {
                var pair = pairs.get(base);
                rna.append(pair);
            } else
                throw new IllegalArgumentException("Not a base: " + base);
        }
        return rna.toString();
    }
}

用函數(shù)樣式編程的關(guān)鍵是,毫不奇怪地,將可能表示為函數(shù)的所有內(nèi)容轉(zhuǎn)換為一個(gè)函數(shù)。所以,讓我們這樣做:

char basePair(char base) {
    if (pairs.containsKey(base))
        return pairs.get(base);
    else
        throw new IllegalArgumentException("Not a base " + base);
}

String transcribe(String dna) {
    var rna = new StringBuilder();
    for (var base : dna.toCharArray()) {
        var pair = basePair(base);
        rna.append(pair);
    }
    return rna.toString();
}

現(xiàn)在,我們可以用地圖作為動(dòng)詞了。在Java中,在Streams API中提供了一個(gè)函數(shù):

char basePair(char base) {
    if (pairs.containsKey(base))
        return pairs.get(base);
    else
        throw new IllegalArgumentException("Not a base " + base);
}

String transcribe(String dna) {
    return dna.codePoints()
            .mapToObj(c -> (char) c)
            .map(base -> basePair(base))
            .collect(
                    StringBuilder::new,
                    StringBuilder::append,
                    StringBuilder::append)
            .toString();
}

那么,讓我們批評(píng)一下這個(gè)解決方案。可以說(shuō)的最好的事情就是循環(huán)已經(jīng)消失了。如果你想一想,循環(huán)是一種文書(shū)活動(dòng),我們真的不應(yīng)該去關(guān)注大部分時(shí)間。通常,我們循環(huán)是因?yàn)槲覀兿霝榧现械拿總€(gè)元素做一些事情。我們真的這里要做的是獲取這個(gè)輸入序列并從它生成一個(gè)輸出序列。流為我們處理迭代的基本管理工作。事實(shí)上,它是一種設(shè)計(jì)模式-一種功能性設(shè)計(jì)模式-但是,我現(xiàn)在還不想提它的名字。我還不想把你嚇跑。

我不得不承認(rèn),代碼的其余部分并沒(méi)有那么好,這主要是因?yàn)镴ava中的原語(yǔ)不是對(duì)象。第一點(diǎn)不偉大的地方是:

mapToObj(c -> (char) c)

我們必須這樣做,因?yàn)镴ava對(duì)原語(yǔ)和對(duì)象的處理方式不同,而且盡管語(yǔ)言確實(shí)為原語(yǔ)設(shè)置了包裝類,但是無(wú)法直接從字符串中獲取字符對(duì)象的集合。

另一個(gè)不那么令人敬畏的地方是:

.collect(
        StringBuilder::new,
        StringBuilder::append,
        StringBuilder::append)

還不清楚為什么要打電話append兩次。我稍后會(huì)解釋,但現(xiàn)在時(shí)機(jī)不對(duì)。

我不打算為這個(gè)密碼辯護(hù)-這太糟糕了。如果有一種方便的方法從字符串中獲取一個(gè)字符流對(duì)象,甚至是一個(gè)字符數(shù)組,那么就沒(méi)有問(wèn)題了,但我們還沒(méi)有得到一個(gè)。在Java中,處理原語(yǔ)不是FP的亮點(diǎn)。想想看,它甚至對(duì)OO編程都沒(méi)有好處。所以,也許我們不應(yīng)該那么癡迷于原語(yǔ)。如果我們把它們?cè)O(shè)計(jì)在代碼之外呢?我們可以為基礎(chǔ)創(chuàng)建一個(gè)枚舉:

enum Base {
    C, G, A, T, U;
}

而且,我們有一個(gè)類作為一個(gè)包含一系列堿基的一流集合:

class Sequence {

    List bases;

    Sequence(List bases) {
        this.bases = bases;
    }

    Stream bases() {
        return bases.stream();
    }
}

現(xiàn)在,Transcriber 看起來(lái)是這樣的:

class Transcriber {

    private Map pairs = new HashMap<>();

    Transcriber() {
        pairs.put(C, G);
        pairs.put(G, C);
        pairs.put(A, U);
        pairs.put(T, A);
    }

    Sequence transcribe(Sequence dna) {
        return new Sequence(dna.bases()
                .map(pairs::get)
                .collect(toList()));
    }
}

這樣好多了。這,這個(gè),那,那個(gè)pairs::get是方法引用;它引用get方法的實(shí)例分配給pairs變量。通過(guò)為基創(chuàng)建類型,我們?cè)O(shè)計(jì)了無(wú)效輸入的可能性,因此需要basePair方法消失,異常也會(huì)消失。這是Java的一個(gè)優(yōu)勢(shì),它本身不能在函數(shù)契約中強(qiáng)制執(zhí)行類型。更重要的是,StringBuilder也消失了。當(dāng)您需要迭代一個(gè)集合、以某種方式處理每個(gè)元素以及構(gòu)建一個(gè)包含結(jié)果的新集合時(shí),Java流是很好的。這可能在你生命中寫(xiě)的循環(huán)中占了相當(dāng)大的比例。大部分家務(wù)活,不是手頭真正工作的一部分,都是為你做的。

在Clojure

撇開(kāi)輸入不足不談,Clojure比Java版本要簡(jiǎn)潔一些,并且它給我們提供了在字符串的字符上進(jìn)行映射的難度。Clojure中最重要的抽象是序列;所有集合類型都可以視為序列,字符串也不例外:

(def pairs {\C, "G",
            \G, "C",
            \A, "U",
            \T, "A"})

(defn- base-pair [base]
  (if-let [pair (get pairs base)]
    pair
    (throw (IllegalArgumentException. (str "Not a base: " base)))))

(defn transcribe [dna]
  (map base-pair dna))

此代碼的業(yè)務(wù)端是最后一行。(map base-pair dna)-這是值得指出的,因?yàn)槟憧赡芤呀?jīng)錯(cuò)過(guò)了。意思是map這,這個(gè),那,那個(gè)base-pair函數(shù)對(duì)dna字符串(表現(xiàn)為序列)。如果我們希望它返回一個(gè)字符串而不是一個(gè)列表,這就是map給我們,唯一需要的改變是:

(apply str (map base-pair dna))

在C#中

讓我們?cè)囋嚵硪环N語(yǔ)言。C#中解決方案的命令式方法如下所示:

namespace RnaTranscription
{
    public class Transcriber
    {
        private readonly Dictionary _pairs = new Dictionary
        {
            {'C', 'G'},
            {'G', 'C'},
            {'A', 'U'},
            {'T', 'A'}
        };

        public string Transcribe(string dna)
        {
            var rna = new StringBuilder();
            foreach (char b in dna)
                rna.Append(_pairs[b]);
            return rna.ToString();
        }
    }
}

同樣,C#沒(méi)有向我們介紹我們?cè)贘ava中遇到的問(wèn)題,因?yàn)镃#中的字符串是可枚舉的,而且所有的“原語(yǔ)”都可以被視為具有行為的對(duì)象。

我們可以一種更實(shí)用的方式重寫(xiě)程序,就像這樣,結(jié)果顯示它比JavaStreams版本要少得多。對(duì)于Java流中的“map”,請(qǐng)改為C#中的“select”:

public string Transcribe(string dna)
{
    return String.Join("", dna.Select(b => _pairs[b]));
}

或者,如果您愿意,可以使用LINQ作為其語(yǔ)法糖:

public string Transcribe(string dna)
{
    return String.Join("", from b in dna select _pairs[b]);
}

我們?yōu)槭裁匆h(huán)?

你可能知道這個(gè)主意。如果您想到以前編寫(xiě)循環(huán)的時(shí)間,通常您會(huì)嘗試完成以下工作之一:

  • 將一種類型的數(shù)組映射為另一種類型的數(shù)組。

  • 通過(guò)查找滿足某種謂詞的數(shù)組中的所有項(xiàng)進(jìn)行篩選。

  • 確定數(shù)組中的任何項(xiàng)是否滿足某些謂詞。

  • 從數(shù)組中累積計(jì)數(shù)、和或其他類型的累積結(jié)果。

  • 將數(shù)組的元素按特定順序排序。

大多數(shù)現(xiàn)代語(yǔ)言中可用的函數(shù)式編程特性允許您完成所有這些功能,而無(wú)需編寫(xiě)循環(huán)或創(chuàng)建集合來(lái)存儲(chǔ)結(jié)果。功能風(fēng)格可以讓你省去那些家務(wù)工作,專注于真正的工作。此外,功能樣式允許您將操作鏈接在一起,例如,如果需要的話:

  1. 將數(shù)組的元素映射到另一種類型。

  2. 過(guò)濾掉一些映射的元素。

  3. 對(duì)過(guò)濾過(guò)的元素進(jìn)行排序。

在命令式風(fēng)格中,這需要多個(gè)循環(huán)或一個(gè)循環(huán),其中包含大量代碼。不管是哪種方式,它都涉及大量的行政工作,這些工作掩蓋了項(xiàng)目的真正目的。在功能風(fēng)格中,您可以分發(fā)管理工作,并直接表達(dá)您的意思。稍后,我們將看到更多的例子,功能風(fēng)格可以使您的生活更輕松。

下次

當(dāng)我學(xué)習(xí)函數(shù)式編程和習(xí)慣JavaStreams API時(shí),每次我寫(xiě)一個(gè)循環(huán)時(shí),我會(huì)做的下一件事就是考慮如何將它重寫(xiě)為流。這通常是可能的。在C#中,ReSharper VisualStudio插件自動(dòng)建議您進(jìn)行這種重構(gòu)。既然我已經(jīng)內(nèi)化了功能風(fēng)格,我就直奔流程,除非我真的需要一個(gè)循環(huán),否則就不需要循環(huán)了。在下一篇文章中,我們將繼續(xù)探索一流的函數(shù),以及如何使用函數(shù)樣式使代碼更具表現(xiàn)力。filterreduce。繼續(xù)關(guān)注!


網(wǎng)站欄目:功能風(fēng)格:Lambda函數(shù)和地圖
分享網(wǎng)址:http://weahome.cn/article/peehhd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部