内射老阿姨1区2区3区4区_久久精品人人做人人爽电影蜜月_久久国产精品亚洲77777_99精品又大又爽又粗少妇毛片

功能樣式:Lambda函數(shù)和映射

一等函數(shù):Lambda函數(shù)和映射

什么是一流的功能?

您之前可能已經(jīng)聽過它說某種特定的語言是有用的,因為它具有“一流的功能”。正如我在本系列關(guān)于函數(shù)式編程的第一篇文章中所說,我不同意這種流行的看法。我同意一流函數(shù)是任何函數(shù)式語言的基本特性,但我不認(rèn)為這是語言功能的充分條件。有很多命令式語言也有此功能。但是,什么是一流的功能?當(dāng)函數(shù)可以被視為任何其他值時,函數(shù)被描述為第一類 - 也就是說,它們可以在運(yùn)行時動態(tài)分配給名稱或符號。它們可以存儲在數(shù)據(jù)結(jié)構(gòu)中,通過函數(shù)參數(shù)傳入,并作為函數(shù)返回值返回。

10年積累的網(wǎng)站設(shè)計制作、成都做網(wǎng)站經(jīng)驗,可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先網(wǎng)站設(shè)計后付款的網(wǎng)站建設(shè)流程,更有普蘭免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

這實際上并不是一個新穎的想法。函數(shù)指針自1972年開始就成為C的一個特性。在此之前,過程引用是Algol 68的一個特性,在1970年實現(xiàn),當(dāng)時,它們被認(rèn)為是一個過程編程特性?;氐竭^去,Lisp(首次在1963年實現(xiàn))建立在程序代碼和數(shù)據(jù)可互換的概念之上。

這些也不是模糊的功能。在C中,我們通常使用函數(shù)作為第一類對象。例如,排序時:

char  ** array  =  randomStrings();

printf(“排序前:\ n”);
for(int  s  =  0 ; s  <  NO_OF_STRINGS ; s ++)
    printf(“%s \ n”,array [ s ]);

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

printf(“排序后:\ n”);
for(int  s  =  0 ; s  <  NO_OF_STRINGS ; s ++)
    printf(“%s \ n”,array [ s ]);

stdlibC中的庫具有針對不同類型的排序例程的函數(shù)集合。所有的人都能夠分揀任何種類的數(shù)據(jù)的:它們從編程器需要的唯一的協(xié)助將被提供,用于比較數(shù)據(jù)集的兩個元素并返回的功能-1,1或者0,指示哪個元件比其它或更大他們是平等的。

這基本上是戰(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);

compare函數(shù)名稱上沒有括號使編譯器發(fā)出函數(shù)指針而不是函數(shù)調(diào)用。因此,將函數(shù)視為C中的第一類對象非常容易,盡管接受函數(shù)指針的函數(shù)的簽名非常難看:

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

函數(shù)指針不僅用于排序。早在.NET發(fā)明之前,就有用于編寫Microsoft Windows應(yīng)用程序的Win32 API。在此之前,有Win16 API。它使得函數(shù)指針的自由使用可以用作回調(diào)。當(dāng)應(yīng)用程序需要通知已發(fā)生的某些事件時,應(yīng)用程序在調(diào)用窗口管理器時由窗口管理器調(diào)用它時提供了指向其自身功能的指針。您可以將此視為應(yīng)用程序(觀察者)與其窗口(可觀察對象)之間的觀察者模式關(guān)系 - 應(yīng)用程序接收到諸如鼠標(biāo)點(diǎn)擊和其窗口上發(fā)生的鍵盤按壓等事件的通知。管理窗戶的工作 - 移動它們,將它們堆疊在一起,決定哪個應(yīng)用程序是用戶操作的接收者 - 在窗口管理器中抽象。應(yīng)用程序?qū)εc其共享環(huán)境的其他應(yīng)用程序一無所知。在面向?qū)ο蟮木幊讨?,我們通常通過抽象類和接口實現(xiàn)這種解耦,但也可以使用第一類函數(shù)來實現(xiàn)。

所以,我們一直在使用一流的功能。但是,可以公平地說,沒有任何語言能夠廣泛宣傳作為一等公民的功能而不是簡單的Javascript。

Lambda表達(dá)式

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

function  myEventListener(){
    警報(“我被點(diǎn)擊了!”)
}
...
var  myBtn  =  document。getElementById(“myBtn”)
myBtn。addEventListener(“click”,myEventListener)

就像在C中一樣,myEventListener在調(diào)用中引用函數(shù)名稱時缺少括號addEventListener意味著它不會立即執(zhí)行。相反,該函數(shù)與所click討論的DOM元素上的事件相關(guān)聯(lián)。單擊該元素時,調(diào)用該函數(shù)并發(fā)出警報。

流行的jQuery庫通過提供一個函數(shù)來簡化流程,該函數(shù)通過查詢字符串選擇DOM元素,并提供有用的函數(shù)來操作元素并向它們添加事件監(jiān)聽器:

$(“#myBtn”)。click(function(){
    警報(“我被點(diǎn)擊了!”)
})

第一類函數(shù)也是實現(xiàn)異步I / O的手段,用于XMLHttpRequest作為Ajax基礎(chǔ)的對象。同樣的想法在Node.js中也無處不在。當(dāng)你想進(jìn)行非阻塞函數(shù)調(diào)用時,你傳遞一個函數(shù)引用,讓它在完成后重新打電話給你。

但是,這里還有其他的東西。其中第二個不僅僅是一流功能的例子。它也是lambda函數(shù)的一個例子。具體來說,這部分:

function(){
    警報(“我被點(diǎn)擊了!”);
}

lambda函數(shù)(通常稱為lambda)是一個未命名的函數(shù)。他們本來可以稱他們?yōu)槟涿瘮?shù),然后每個人都會立即知道它們是什么。但是,這聽起來并不令人印象深刻,所以lambda的功能就是它!lambda函數(shù)的關(guān)鍵是你需要在那個地方只有那里的函數(shù); 因為在其他地方不需要它,你只需在那里定義它。它不需要名字。如果您確實需要在其他地方重用它,那么您可以考慮將其定義為命名函數(shù)并通過名稱引用它,就像我在第一個Javascript示例中所做的那樣。沒有l(wèi)ambda函數(shù),使用jQuery和Node編程確實非常煩人。

Lambda函數(shù)以不同的方式用不同的語言定義:

在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中,所謂的“stabby”語法: -> (a, b) { return a + b }

正如我們所看到的,大多數(shù)語言都比Javascript更簡潔地表達(dá)lambda。

地圖

您可能已經(jīng)在編程中使用術(shù)語“map”來表示將對象存儲為鍵值對的數(shù)據(jù)結(jié)構(gòu)(如果您的語言將其稱為“字典”,那么很好 - 沒問題)。在函數(shù)式編程中,該術(shù)語具有另外的含義。實際上,基本概念實際上是一樣的。在這兩種情況下,一組事物被映射到另一組事物。在數(shù)據(jù)結(jié)構(gòu)的意義上,地圖是名詞 - 鍵被映射到值。在編程意義上,映射是動詞 - 函數(shù)將值數(shù)組映射到另一個值數(shù)組。

假設(shè)你有一個函數(shù)f和一個值數(shù)組A = [ a1,a2a3,a4 ]。要映射?F超過意味著應(yīng)用?F在每個元件

  • a1 → fa1)= a1'

  • a2 → fa2)= a2'

  • a3 → fa3)= a3'

  • a4 → fa4)= a4'

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

A' = map(fA)= [ a1',a2'a3',a4' ]

按示例地圖

好的,所以這很有趣但有點(diǎn)數(shù)學(xué)。你多久會這樣做?實際上,它比你想象的要頻繁得多。像往常一樣,一個例子最好地解釋了事情,所以讓我們來看看我在學(xué)習(xí)Clojure時從exercism.io中提取的一個簡單的練習(xí)。這項運(yùn)動被稱為“RNA轉(zhuǎn)錄”,它非常簡單。我們將看一下需要轉(zhuǎn)換為輸出字符串的輸入字符串?;胤g如下:

  • C→G

  • G→C

  • A→U

  • T→A

除C,G,A,T以外的任何輸入均無效。JUnit5中的測試可能如下所示:

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();
        斷言(轉(zhuǎn)錄者。轉(zhuǎn)錄(dna),是(rna));
    }

    @測試
    void  reject_invalid_bases(){
        var  transcriber  =  new  Transcriber();
        assertThrows(
                IllegalArgumentException。上課,
                ()- >  抄寫員。轉(zhuǎn)錄(“XCGFGGTDTTAA”));
    }
}

而且,我們可以通過這個Java實現(xiàn)來完成測試:

class  Transcriber {

    private  Map < Character,Character >  pairs  =  new  HashMap <>();

    Transcriber(){
        對。放('C','G');
        對。put('G','C');
        對。放('A','U');
        對。put('T','A');
    }

    String  transcribe(String  dna){
        var  rna  =  new  StringBuilder();
        對于(VAR  基:DNA。toCharArray()){
            如果(對。的containsKey(基)){
                var  pair  =  pair。得到(基礎(chǔ));
                rna。追加(對);
            } 其他
                拋出 新的 IllegalArgumentException(“不是基數(shù):”  +  基數(shù));
        }
        返回 rna。toString();
    }
}

不出所料,將功能風(fēng)格編程的關(guān)鍵是將可能表達(dá)為函數(shù)的所有內(nèi)容轉(zhuǎn)換為一個函數(shù)。所以,讓我們這樣做:

char  basePair(char  base){
    if(pairs。包含Key(base))
        回歸 對。得到(基礎(chǔ));
    其他
        拋出 新的 IllegalArgumentException(“不是基礎(chǔ)”  +  基礎(chǔ));
}

String  transcribe(String  dna){
    var  rna  =  new  StringBuilder();
    對于(VAR  基:DNA。toCharArray()){
        var  pair  =  basePair(base);
        rna。追加(對);
    }
    返回 rna。toString();
}

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

char  basePair(char  base){
    if(pairs。包含Key(base))
        回歸 對。得到(基礎(chǔ));
    其他
        拋出 新的 IllegalArgumentException(“不是基礎(chǔ)”  +  基礎(chǔ));
}

String  transcribe(String  dna){
    返回 dna。codePoints()
            。mapToObj(c  - >(char)c)
            。地圖(基地 - >  basePair(基地))
            。收集(
                    StringBuilder :: new,
                    StringBuilder :: append,
                    StringBuilder :: append)
            。toString();
}

Hmmmm

所以,讓我們批評這個解決方案。關(guān)于它的最好的事情是循環(huán)已經(jīng)消失了。如果你考慮一下,循環(huán)是一種文書活動,我們真的不應(yīng)該在大多數(shù)時候關(guān)注它。通常,我們循環(huán)是因為我們想為集合中的每個元素做一些事情。我們真正想要做的是獲取此輸入序列并從中生成輸出序列。Streaming負(fù)責(zé)為我們迭代的基本管理工作。事實上,它是一種設(shè)計模式 - 一種功能性的設(shè)計模式 - 但是,我還沒有提到它的名字。我還不想嚇唬你。

我不得不承認(rèn)代碼的其余部分并不是那么好,主要是因為Java中的原語不是對象。第一點(diǎn)非偉大是這樣的:

mapToObj(c  - >(char)c)

我們必須這樣做,因為Java以不同的方式處理原語和對象,雖然該語言確實具有基元的包裝類,但是無法直接從String獲取Character對象的集合。

另一點(diǎn)不那么令人敬畏的是:

。收集(
        StringBuilder :: new,
        StringBuilder :: append,
        StringBuilder :: append)

很明顯為什么有必要再打append兩次電話。我稍后會解釋,但現(xiàn)在時間不對。

我不會試圖捍衛(wèi)這個代碼 - 它很糟糕。如果有一種方便的方法從String,甚至是一個字符數(shù)組中獲取Stream of Character對象,那么就沒有問題了,但我們并沒有幸運(yùn)。處理原語并不是Java中FP的最佳選擇。想想看,它對OO編程來說甚至都不好。所以,也許我們不應(yīng)該如此著迷原始人。如果我們從代碼中設(shè)計出來怎么辦?我們可以為基數(shù)創(chuàng)建一個枚舉:

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

而且,我們有一個類作為一個包含一系列基礎(chǔ)的一流集合:

class  Sequence {

    列出< 基地>  基地 ;

    序列(List < Base >  bases){
        這個。堿 =  堿 ;
    }

    Stream < Base >  bases(){
        返回 基地。stream();
    }
}

現(xiàn)在,  Transcriber 看起來像這樣:

class  Transcriber {

    private  Map < Base,Base >  pairs  =  new  HashMap <>();

    Transcriber(){
        對。放(C,G);
        對。放(G,C);
        對。放(A,U);
        對。put(T,A);
    }

    序列 轉(zhuǎn)錄(序列 dna){
        返回 新的 序列(DNA?;兀ǎ?/pre>
                。map(pairs :: get)
                。collect(toList()));
    }
}

這要好得多。這pairs::get是一個方法參考; 它指的是get分配給pairs變量的實例的方法。通過為基礎(chǔ)創(chuàng)建類型,我們設(shè)計了無效輸入的可能性,因此對該basePair方法的需求消失,異常也是如此。這是Java對Clojure的一個優(yōu)勢,它本身不能在函數(shù)契約中強(qiáng)制執(zhí)行類型。更重要的是,它StringBuilder也消失了。當(dāng)您需要迭代集合,以某種方式處理每個元素并構(gòu)建包含結(jié)果的新集合時,Java Streams非常適合。這可能占你生活中所寫循環(huán)的很大一部分。大部分的家務(wù)管理都不是真正的工作的一部分,而是為您完成的。

在Clojure

缺少打字,Clojure比Java版本更簡潔,它給我們映射字符串字符沒有任何困難。Clojure中最重要的抽象是序列; 所有集合類型都可以視為序列,字符串也不例外:

(def  對 { \ C , “ G” ,
            \ G , “ C” ,
            \ A , “ U” ,
            \ T , “ A” } )

(defn  -base-pair  [ base ]
  (if-let  [ pair  (get  pairs  base )]
    對
    (throw  (IllegalArgumentException。 (str  “ not base:”  base )))))

(定義 轉(zhuǎn)錄 [ dna ]
  (地圖 基礎(chǔ)對 dna ))

這段代碼的業(yè)務(wù)結(jié)束是最后一行(map base-pair dna)- 值得指出,因為你可能錯過了它。它表示字符串上mapbase-pair函數(shù)dna(表現(xiàn)為序列)。如果我們希望它返回一個字符串而不是一個列表,這就是map我們所要求的,唯一需要做的改變是:

(應(yīng)用 str  (map  base-pair  dna ))

在C#中

我們來試試另一種語言。C#中解決方案的必要方法如下所示:

命名空間 RnaTranscription
{
    公共 類 轉(zhuǎn)錄員
    {
        private  readonly  Dictionary < char,char >  _pairs  =  new  Dictionary < char,char >
        {
            { 'C','G' },
            { 'G','C' },
            { 'A','U' },
            { 'T','A' }
        };

        public  string  Transcribe(string  dna)
        {
            var  rna  =  new  StringBuilder();
            的foreach(炭 b  中 的DNA)
                rna。追加(_pairs [ b ]);
            返回 rna。ToString();
        }
    }
}

同樣,C#沒有向我們展示我們在Java中遇到的問題,因為C#中的字符串是可枚舉的,并且所有“基元”都可以被視為具有行為的對象。

我們可以用更加實用的方式重寫程序,就像這樣,并且它比Java Streams版本要簡單得多。對于Java流中的“map”,請在C#中讀取“select”:

public  string  Transcribe(string  dna)
{
    return  String。加入(“”,dna。選擇(b  =>  _pairs [ b ]));
}

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

public  string  Transcribe(string  dna)
{
    return  String。加入(“” ,從 b  中 的DNA  選擇 _pairs [ b ]);
}

為什么我們循環(huán)?

你可能會得到這個想法。如果您想到編寫循環(huán)之前的時間,通常您會嘗試完成以下任一操作:

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

  • 通過查找滿足某個謂詞的數(shù)組中的所有項來進(jìn)行過濾。

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

  • 累積數(shù)組中的計數(shù),總和或其他類型的累積結(jié)果。

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

大多數(shù)現(xiàn)代語言中提供的函數(shù)式編程功能使您無需編寫循環(huán)或創(chuàng)建集合來存儲結(jié)果即可完成所有這些操作。功能樣式允許您省去這些內(nèi)務(wù)操作并專注于實際工作。更重要的是,功能樣式允許您將操作鏈接在一起,例如,如果您需要:

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

  2. 過濾掉一些映射的元素。

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

在命令式樣式中,這需要多個循環(huán)或一個循環(huán),其中包含很多代碼。無論哪種方式,它涉及許多模糊程序真正目的的管理工作。在功能風(fēng)格中,您可以免除管理工作并直接表達(dá)您的意思。稍后,我們將看到更多功能樣式如何讓您的生活更輕松的例子。

名稱欄目:功能樣式:Lambda函數(shù)和映射
本文鏈接:http://www.rwnh.cn/article32/gpogsc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動態(tài)網(wǎng)站、營銷型網(wǎng)站建設(shè)、網(wǎng)站建設(shè)、企業(yè)建站域名注冊、App開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

營銷型網(wǎng)站建設(shè)
南溪县| 东宁县| 阿勒泰市| 于都县| 克什克腾旗| 阆中市| 荥经县| 呼图壁县| 固镇县| 高州市| 修文县| 荔波县| 长寿区| 安达市| 中宁县| 从化市| 阳谷县| 嘉祥县| 香港| 博罗县| 三明市| 镇宁| 柳林县| 新宁县| 曲周县| 探索| 东阳市| 旬阳县| 赤壁市| 海城市| 宁乡县| 西丰县| 盐边县| 内丘县| 永州市| 涞源县| 枣阳市| 灵丘县| 左云县| 临沂市| 文昌市|