杭州線下分享:First class function in Go

2021-02-19 Go語言充電站
合照

6月2日Go語言中文網在杭州舉辦了線下的MeetUp活動,這次活動辦很成功,感謝站長polaris在杭州舉辦活動的提議,感謝Seekload的籌備與主持,感謝Aaron提供場地,感謝所有到場者的技術經驗分享,沒有你們就沒有這次精彩的活動。

在活動上,我做了個主題分享,今天把分享整理成文章,分享給學習Go語言的各位朋友。

參加本次活動的朋友,大多是剛接觸Go,少數幾個朋友把玩Go 2~3年了,所以我把主題定位到能讓所有人聽懂的主題。另外,大家所處行業各有不同,這就要求專注介紹Go本身的特性,這才是大家通用的地方。

最後選題為First class function in Go,這次沒有做中文翻譯,避免翻譯後有誤解。這個特性淺顯易懂,但掌握Go語言的思維,才能把它用好。

線下分享後,證明選題選對了,大家都能聽懂,所以現在不了解First class function的朋友不用著急,後面我會層層推進的方式介紹,相信你一定能理解,那就進入正文吧。

目錄優先奉上

First class function in Go概念介紹定義函數類型聲明函數類型的變量和為變量賦值高階函數函數作為其他函數入參函數作為返回值+動態創建匿名函數閉包Demo場景介紹版本1版本2版本3總結源碼PPT下載雲象介紹活動總結

First class function in Go概念介紹幻燈片04

某個程式語言擁有First class function特性指可以把函數作為變量對待。也就說,函數與變量沒有差別,它們是一樣的,變量出現的地方都可以替換成函數,並且編譯也是可以通過的,沒有任何語法問題。

在Go裡,變量可以存在於哪些地方?

幻燈片05

變量可以被聲明、定義,可以使用type創建變量的類型,可以作為函數的入參和返回值,可以存在slice, array, map等數據結構裡,可以被動態的創建。

在Go中,函數也可以被聲明、定義,可以使用type創建一個函數類型,可以作為其他函數的入參和返回值,可以保存在其他類型的數據結構裡,最後,函數是可以被動態創建的。

簡要歸類一下就是下圖的樣子,除了上面提到的內容,還有匿名函數和閉包,將按下圖順序介紹每一個小特性。

幻燈片06定義函數類型幻燈片07

使用type定義一個函數類型,type後是類型名稱,本例中是Operation,再後面是類型的定義,對於函數而言,被稱為signature,即函數籤名,這個函數籤名表示:Operation類型的函數,它以2個int類型為入參,以1個int為返回值。所有滿足該函數籤名的函數,都是Operation類型的函數。

幻燈片08

函數Add和Sub都符合Operation的籤名,所以Add和Sub都是Operation類型。

聲明函數類型的變量和為變量賦值幻燈片09

變量op是Operation類型的,可以把Add作為值賦值給變量op,執行op等價於執行Add。

高階函數

高階函數分為函數作為入參和函數作為返回值2部分。

函數作為其他函數入參幻燈片10

定義一個Calculator結構體,它始終保持計算後的結果。

它有一個方法Do,入參為一個Operation類型的函數op和1個int類型的變量a,使用計算器的值c.v和a作為op的入參,進行指定運算,並把結果保存會c.v。

main中,聲明了一個變量calc,calc.v初始值為0,然後運行了加1和減2的操作,加減法的完成使用的我們之前定義的函數Add和Sub。操作等價於:

1calc.v = Add(calc.v, 1)
2calc.v = Sub(calc.v, 2)

函數作為返回值+動態創建幻燈片11

這次,改變Operation的定義,修改為接收1個int類型的入參,返回1個int類型的返回值。

同時修改函數Add和Sub,它們接收1個int類型的入參,返回1個Operation類型的函數,這個函數是動態創建出來的。

以Add為例介紹,在Add裡動態創建了一個函數,

1func(a int) int {
2    return a + b
3}

該函數實現了在變量a基礎上加b的操作,並返回結果,我們把這個函數賦值給變量addB,把addB作為返回值返回。

所以本例實現了以函數作為返回值和動態創建函數。

幻燈片12

Operation,Add和Sub修改後,Calculator也要同步修改,方法Do修改為只接收Operation類型的函數。

main函數裡,注意Do的入參:Add(1),它實現的效果是,創建了1個函數,該函數接收1個值,然後把這個值+1返回,如果用數學表示就是這樣:

1
2add1(x) = x + 1

同理,Sub(2)的數學表示如下:

1
2sub2(x) = x - 2

所以2次Do操作等價於:

1
2calc.v = add1(calc.v)
3
4calc.v = sub2(calc.v)

匿名函數幻燈片13

上圖左邊是普通函數,func後為函數名,然後為函數籤名。右邊只有func和函數籤名,缺少函數名,右邊的情況為匿名函數。

幻燈片14

以Add函數其中定義的函數為例:

1func(a int) int {
2    return a + b
3}

這就是1個匿名函數,它沒有名字。addB並不是函數的名字,只是1個變量名而已,只不過這個變量名的類型是沒有顯示定義出來的

Add通常簡寫為右邊的形式。

閉包幻燈片15

很多人搞不清什麼是匿名函數,什麼是閉包,所以這裡分開介紹這2個概念。

閉包指有權訪問另一個函數作用域中的變量的函數。大白話就是,可以創建1個函數,它可以訪問其他函數遍歷,但不需要傳值。

仍然是Add函數為例,比如匿名函數裡直接使用了變量b,該匿名函數也是閉包函數。

閉包的特性註定了,閉包函數要定義在一個函數裡面,定義在一個函數裡面又只能是匿名函數。

那,匿名函數和閉包是不是就等價了?

No,一個函數可以是匿名函數,但不是閉包函數,因為閉包有時是有副作用的。

幻燈片16

我們想並發的把sl中的值列印出來,結果為何會是右邊這樣?

因為並發的匿名函數,使用的是test1中的i,v,即這是閉包函數,所有的goroutine都共享這2個值,並且啟動1個goroutine後,這2個值變為下一個位置的值。你運行的結果也許不是9 9 9….,因為這個goroutine的調度有關。

如何才能符合預期的列印?只使用匿名函數進行傳值,不使用閉包。

幻燈片17Demo

接下來以一個實際的場景,和3種實現版本看如何用Go的思維去解決問題。

場景介紹幻燈片19

做Go語言工作,尤其是跟網絡打交道的工作,連接管理是逃不開的。我做區塊鏈相關的技術工作,區塊鏈中也有網絡管理,所以我就以區塊鏈的網絡管理為場景進行介紹,但不涉及具體的技術細節,大家莫慌,只需要理解2個概念就行。

區塊鏈是構建在P2P網絡之上,在P2P網絡中:

一個節點即可以是伺服器也可以是客戶端,被稱為Host

和本節點連接的所有節點都被稱為Peer

具體的場景是:Host需要保存所有建立連接的Peer,並對這些Peer進行維護:增加和刪除Peer,並且提供Peer的查詢和向所有Peer廣播消息的接口

針對這個問題場景,我寫了3個版本的Demo,我們依次來介紹,再看的時候,可以思考其中的不同。

版本1幻燈片20

先看Peer定義,Peer中保存了ID,我們可以通過ID來表示全網中所有的節點,Peer中還有其他欄位,比如網絡連接、地址、協議版本等信息,此處已經省略掉。

Peer有一個WriteMsg的方法,實現向該Peer發送消息的功能,例子中使用列印替代。

Peer的定義在3個版本中都不會發生變化,所以後面就不再展示

幻燈片21

Host通過peers保存了所有連接的Peer,可以通過Peer.ID對Peer進行索引。Peer的管理是並發場景,比如,我們可能同時接收到多個Peer的連接,又同時需要向所有Peer廣播消息,需要對peers加鎖保護。最後,我們省略了Host的其他欄位。

NewHost()用來創建一個Host對象,用來代表當前節點。

友情提醒:Host在每一個版本都會不同。

幻燈片22

Host有4個方法,分別是:

AddPeer: 增加1個Peer。

RemovePeer: 刪除1個Peer。

GetPeer: 通過Peer.ID查詢1個Peer。

BroadcastMsg: 向所有Peer發送消息。

每一個方法都需要獲取lock,然後訪問peers,如果只讀取peers則使用讀鎖。

第1個版本已經介紹完了,大家可以思考一下版本1的缺點。

幻燈片23

第1個版本跟其他語言實現其實沒有本質區別,用C++、Java等也能寫出上面邏輯的代碼,只不過這個是Go語言實現的罷了。

這個版本是一個communicate by sharing memory的體現,具體來講,每個goroutine都是1個實體,它們同時運行,調用Host的不同方法來訪問peers,只有拿到當前lock的goroutine才能訪問peers,仿佛當前goroutine在同其他goroutine講:我現在有訪問權,你們等一下。本質上就是,通過共享Host.lock這塊內存,各goroutine進行交流(表明自己擁有訪問權)。

版本2幻燈片24

很多Go老手都聽過這句話了,這是Go的「聯合創始人」Rob Pike某個會議上說的。

在Go中,推薦使用CSP實現並發,而不是習慣性的使用Lock,使用channel傳遞數據,達到多goroutine間共享數據的目的,也就是share memory by communicating

所以,我們版本2,就使用channel的方式,來實現Peer的管理。

幻燈片25

在版本1中,peers是大家都想訪問的,並且Host有4個方法,畫到了上面的圖中,我們看下怎麼用CSP實現。

peers需要在單獨的goroutine中,其他的4個方法在其他的goroutine中調用,它們之間進行通信。

我對使用CSP有一個好的實踐,就是把數據流動畫出來,並把要流動的數據標上,然後那些數據流動的線條,就是channel,線條上的數據就是channel要傳遞的數據,圖中也把這些線條和數據標上了。具體的細節,可以識別圖片中的二維碼,看看這篇老文,還有就是並不是所有的並發場景都適合使用channel,有些用鎖更好,這篇文章也有介紹。

幻燈片26

重新定義Host,增加了4個channel,從上到下分別用於增加Peer、廣播消息、刪除Peer和停止Host。

幻燈片27

Host增加了2個方法:

Start()用於啟動1個goroutine運行loop(),loop保存所有的peers。

Stop()用於關閉Host,讓loop退出。

幻燈片28

左邊是loop()的實現,它從4個channel裡接收數據,然後做不同的操作。

右邊是AddPeer, RemovePeer, BroadcastMsg的實現。

利用1分鐘的事件,左右兩邊對照著看,理解增加1個Peer的全過程。

這就是版本2的全部實現了,思考一下版本2有什麼問題,原因是啥?

幻燈片29幻燈片30

問題就是我們沒有實現GetPeer這個方法,聰明的你一定在Host的定義就發現了,只有增加、刪除和廣播消息的channel。

沒能實現GetPeer的原因下圖中進行了介紹,你有沒有解決辦法?

幻燈片31幻燈片32幻燈片33

可能會有很多goroutine調用GetPeer,我們需要向每一個goroutine發送結果,這就需要每一個goroutine都需要對應的1個接收結果的channel。

所以我們可以增加1個query channel,channel裡傳遞Peer.ID和接收結果的channel。

還有沒有其他辦法?我們今天的主題First class function還有入場,你有辦法用這個特性實現嗎?

版本3幻燈片34

First class function: 函數可以向變量一樣使用。那channel裡面是不是可以傳遞函數呢?當然可以。

幻燈片35

我們可以建立一個channel,用這個channel向loop傳遞操作peers的函數,所以函數的入參是peers map[string]*Peer,無需返回值,因為函數是在loop裡面調用的,調用AddPeer等函數的goroutine是接收不到返回值的。我們把這個類型的函數定義為Operation。

Host修改為只有2個channel,stop功能如版本2,opCh用來傳遞Operation類型的函數。

幻燈片36

loop函數可以簡化為左邊的形式了,右邊是AddPeer和RemovePeer,以AddPeer為例進行介紹,創建了一個匿名函數,向peers裡增加p,然後把函數發送到opCh。

幻燈片37

BroadcastMsg與AddPeer類似。

幻燈片38

我們重點看一下GetPeer,創建了retCh用於接收查詢的結果,創建了匿名函數進行查詢,並把查詢結果發送到retCh,然後啟動1個goroutine把匿名函數寫入到opCh,最後等待從retCh讀取查詢結果。

這樣就實現了向每個調用GetPeer的goroutine發送查詢結果。

總結幻燈片40

總結都在上面了,不多說了。

友情提醒:這3種方式本身並無優劣之分,具體要用那種實現,要依賴自身的實際場景進行取捨。

源碼

識別下圖二維碼。

幻燈片41PPT下載

下載連結:http://img.lessisbetter.site/Go%E8%AF%AD%E8%A8%80%E6%80%9D%E7%BB%B4First-class-function.pdf

閱讀原文下載。

雲象介紹

廣告時間,雲象區塊鏈持續招人,歡迎來撩。

幻燈片42活動總結

最後奉上Seekload關於本次活動的總結:Gopher杭州線下面基第一期。

相關焦點

  • C++ std::function技術淺談
    first_argument_type如果函數對象有兩個個參數,那麼這個代表第一個參數類型。second_argument_type如果函數對象有兩個個參數,那麼這個代表第二個參數類型。(sometimes) typedef _Ty1 first_argument_type; typedef _Ty2 second_argument_type; };template<class _Ret, class...
  • 如何理解藥品first in class
    藥品投資中有很多術語,初學者在初次接觸的時候總是一頭霧水,比如說我們總是在一些創新藥投資中見到first in class,best in class ,me too 等詞語,今天我們就來介紹一下這些名詞具體含義。
  • Go 中的函數是一等公民,這到底在說什麼?
    看來有必要解釋下什麼是一等公民。再往下看之前,你能說出什麼是一等公民嗎?關於一等公民[1](First-class citizen)看看維基百科的定義:In programming language design, a first-class citizen (also type, object,
  • Buy a first class airline ticket什麼意思?
    Buy a first class airline ticket什麼意思?怎樣用英語說first class ticket的意思?比如,Buy a first class airline ticket什麼意思?
  • 中國first in class新藥研發 還缺什麼?
    在這等趨勢下,本土創新藥出路何在?目前來看,first in class是可能的答案。First in class中國製造:沒人做,還是沒做好?業內對first in class的界定通常是指針對全球首創靶點或機制的創新藥開發。
  • 中國first in class新藥研發,還缺什麼?
    在這等趨勢下,本土創新藥出路何在?目前來看,first in class是可能的答案。  First in class中國製造:沒人做,還是沒做好?  業內對first in class的界定通常是指針對全球首創靶點或機制的創新藥開發。
  • jQuery的first()、last()、eq()、filter()和not()方法過濾元素
    02第2節:first()方法在jQuery中,使用first()方法獲取當前元素所在集合的第一個元素,first()方法只能返回單個的一個Html元素。我們可以使用jQuery選擇器獲取一個Html元素的集合,然後再使用first()得到集合中的第一個元素。現在舉一個例子,演示一下first()方法的使用:<!
  • 千萬別對老闆說「You go first」
    會不會順口說出「You go first 」?小編在這裡告訴你,「You go first 」是典型的中式英文的表達方式,表達的語氣不禮貌哦!那該怎麼表達?來看今天的分享吧。You go first. ×After you. 用法:為什麼you go first 是錯的?
  • (LBL30)聽力:你以為first class是「第一節課」的意思?
    錄音原文You've got a choice of first and second class and there's a buffet car - though refreshments are included in the cost of a first class ticket.
  • 【乾貨】Vue的class語法與常規語法對照表
    語法:import { Vue, Component } from 'vue-property-decorator'@Componentexport default class HelloWorld extends Vue { private firstName = 'Mark' private lastName = 'Twain'
  • 詞彙|first與at first
    first與at first用法區別詳解:1、從詞性上看區別first可用作形容詞或副詞,有時還可用作代詞;而at first作為介詞短語,只起副詞用作用(在句中用作狀語)。如:First class is the most expensive way to travel. 坐頭等艙是最貴的旅行方式。(first為形容詞,在句中用作定語)This is the first time I have heard of such things. 這是我第一次聽到這樣的事。
  • Shenzhen metro to offer first-class seating
    Deputy Secretary of the local government Zhao Penglin confirmed the plan on Thursday, saying the first-class compartments will ensure each passenger has a seat, at the cost of twice as much as a
  • Mayor Cao Lijun Highlights Construction of First-class...
    university will be aimed at the latest development of relevant subjects, improve scientific research levels, strengthen cooperation between the university and the local government and strive to become a first-class
  • ...Master’s Degree Authorization Stations in First-class...
    State Council on Dynamically Adjusting the List of Degree Authorization Stations Revoked and Added in 2019, and Northeastern University was approved to add the Master’s degree authorization stations in first-class
  • 對老外說「You go first」,其實很不禮貌
    您先請 ≠ You go first為什麼You go first 是錯的?我們是禮儀之邦,進門或進電梯,通常會讓對方先走,但這時不能說You go first. 這句在老外聽來有些刺耳,它像是在命令,外國人的邏輯跟我們是反的,他們會用After you例句:After you.
  • 7 types of foreigners in Chinese class
    Below are the types of people you see in Chinese class. Feel free to add the types you know that I did not include in the list.
  • typescript中的class和interface
    ES5編輯後的結果var Person = /** @class */ (function () { function Person(name) { this.name = name; } Person.prototype.getName = function
  • 不吃「快餐」、只爭first in class的康乃德
    在自身免疫領域浸淫多年,只爭first in class「要想在一個領域有所建樹,有競爭力,必須做自己熟悉且市場前景大的領域。」鄭偉表示道。2012年成立的康乃德,是潘武賓的第二次創業,但是鄭偉首次自主創業。
  • 重新定義OMO模式,DBclass實現線上線下課堂靈動融合
    重新定義OMO模式,DBclass實現線上線下課堂靈動融合一次新冠疫情讓五花八門的線上教育平臺一片火熱,在線教育替代不了線下教育,估計已經萬千教育從業者的共識,只有做好線上線下靈動結合的OMO模式,才能可持續的發展,怎樣來實現靈動融合呢,DBclass值得每家面向未來的教培機構認真考察。
  • 記住:對老外說「You go first」 其實很不禮貌!
    比如下面這些:  1.您請,你走前面  You go first.  為什麼you go first 是錯的?  我們是禮儀之邦,進門或進電梯,通常會讓對方先走,但這時不能說You go first.  2.我先走  I go first.