restapi(7)- 談談函數式編程的思惟模式和習慣

  國慶前,參與了一個c# .net 項目,真正從新體驗了一把搬磚感受:在一個多月時間好像不加任何思考,不斷敲鍵盤加代碼。我想,這也許是行業內大部分中小型公司程序猿的真實寫照:都是坐在電腦前的搬磚工人。不過也不是沒有任何收穫,在搬磚的過程當中我彷佛發現了一些現象和形成這些現象背後的緣由及OOP思惟、習慣模式。和大部分IT公司同樣,這間公司在行業裏存在了必定時間(不是初創)因此在產品和技術方面有必定的積累,通俗點就是一堆現成的c# .net 代碼。而後就是項目截止日期壓力。爲了按時完成任務的我只能在原有代碼基礎上不斷加功能,根本沒有機會去考慮用什麼樣的代碼模式、結構去達到更好的效果。在這個過程當中有個有趣的現象引發了個人注意:基本上我只需按照某種流程(多數是業務需求)一個個增長環節就能夠實現一項完整功能,固然我是不會計較這些環節對軟件其它部分是否產生影響,又或者之後代碼維護會不會很麻煩,只要能及時交貨就行。想一想這種作法偏偏是面向對象編程或所謂行令式編程的特色,即:經過逐行執行命令引導程序的狀態改變,最終狀態就是運行程序的結果了,或者就是功能的實現了。經過一行行增長代碼最終總會到達預期的狀態,不是嗎。這正是OO編程的思惟模式:由於程序狀態體如今每行代碼上,隨時能夠檢查,驗證思路,因此OOP比較容易上手(相對函數式編程而言)。前端

        回顧一下函數式編程:好像很難按照天然邏輯思惟順序來實現一個功能,這是由於函數式編程是一種嵌套式間接性的編程模式,即程序是在某種嵌套裏運行的。函數式編程又被稱爲monatic-programming,即在monad裏編程。monad就是我所說的嵌套,是一種類型結構,最經常使用的是Future類型。在現代編程裏多線程編程很是廣泛,實際上每每咱們離不開各類各樣的Future。舉個形象的例子:若是實現把髒水從A點引到B點輸出純淨水做爲某種函數式程序,編程如同搭建管道網。必須首先準備好各式各樣功能的喉管,實現每種喉管的特殊功能如過濾、消毒等,而後再鏈接組合造成送水管道。mongodb

      我在進行函數式編程時老是要把因此問題前先後後都考慮清楚了才能開始動手。首先會把一項功能的全部環節先總結出來,這些都是一些函數。而後嘗試把這些函數的類型統一了,就像上面提到的喉管同樣,由於不一樣規格的喉管是沒法鏈接的。一樣,不一樣類型的嵌套monad是沒法實現函數組合的。而後先根據需求實現這些函數的輸入輸出,最後把這些函數組合起來造成完整功能。你看,在函數式編程裏是沒法作到隨意想到那就寫到那的,必須先進行總體的思量。因此,函數式編程在代碼重用和維護上有先天的優點。這個例子也體現了函數式編程的思惟模式。數據庫

   下面我想用一個實際的例子來示範函數式編程模式:前面幾篇討論的例子裏有一個是把前端httpclient上傳httpserver的圖片存放入服務器端mongodb數據庫的。如今發現客戶端上傳圖片數據流有困難,但願上傳一個圖片下載網址,由httpserver自行下載圖片並寫入mongodb。單從這個功能來說,應該由幾個環節組成:編程

一、從上傳的數據中抽出圖片下載網址json

二、下載圖片,經過http的request請求,從response裏獲取圖片數據流c#

三、經過mongodb的count功能獲取圖片系列序號數組

四、將圖片寫入mongodb服務器

首先,我須要把這幾個環節造成函數,而後統一函數類型。無可爭議,最好選擇Future[A]這樣的函數返回類型:數據結構

假設數據是用json格式傳上來的,那得有個類型做爲數據結構:多線程

 

  case class UpData (pid: String, url: String)

 

能夠以下獲取上傳的數據:

 entity(as[String]) { json => val upData: UpData = fromJson[UpData](json) ... }

獲取圖片系列序號:返回Future[Long]

 

 

repository.count(upData.pid).toFuture[Long]

 

下載圖片:這個返回Future[ByteString]

 

 import akka.actor.ActorSystem import akka.http.scaladsl.model._ import akka.http.scaladsl.Http def downloadPicture(url: String)(implicit sys: ActorSystem): Future[ByteString] = { val dlRequest = HttpRequest(HttpMethods.GET, uri = url) Http(sys).singleRequest(dlRequest).flatMap { case HttpResponse(StatusCodes.OK, _, entity, _) => entity.dataBytes.runFold(ByteString()) { case (hd, bs) => hd ++ bs } case _ => Future.failed(new RuntimeException("failed getting picture!")) } }

寫入mongodb:這個函數也返回Future[?]

 def addPicuture(pid: String,seqno: Int, optDesc: Option[String] ,optWid:Option[Int],optHgh:Option[Int], bytes: Array[Byte]):Future[Completed] ={ var doc = Document( "pid" -> pid, "seqno" -> seqno, "pic" -> bytes ) if (optDesc != None) doc = doc + ("desc" -> optDesc.get) if (optWid != None) doc = doc + ("width" -> optWid.get) if (optHgh != None) doc = doc + ("height" -> optHgh.get) repository.insert(doc).toFuture[Completed] }

好了,如今這幾個函數都是Future類型的,能夠進行組合了:

            val futSeqno: Future[Long] = for { cnt <- repository.count(upData.pid).toFuture[Long] barr <- downloadPicture(upData.url) _ <- addPicuture(upData.pid, cnt.toInt, None, None, None, barr.toArray) } yield cnt

futSeqNo是個組合的運算流程。注意它的類型仍是future:意味這咱們沒法預測這個運算何時會完成,特別若是下載一張超大圖片又或者網速緩慢的話,極可能在下載完成以前就執行了complete()。因此咱們必須保證圖片下載完成後才向終端httpclient返回response,就用onComplete來實現:

 onComplete(futSeqno) { case Success(lv) => complete(lv.toString()) case _ => complete("error saving picture!") }

因此整段宏觀代碼以下:

 post { entity(as[String]) { json => val upData: UpData = fromJson[UpData](json) val futSeqno: Future[Long] = for { cnt <- repository.count(upData.pid).toFuture[Long] barr <- downloadPicture(upData.url) _ <- addPicuture(upData.pid, cnt.toInt, None, None, None, barr.toArray) } yield cnt onComplete(futSeqno) { case Success(lv) => complete(lv.toString()) case _ => complete("error saving picture!") } } }~

是否是很容易讀懂理解?實際上咱們把複雜的細節函數藏在背後。而這些函數是高度可重複利用的,這也是咱們在動手以前通盤考慮的成果。

相關文章
相關標籤/搜索