天天看點

這裡我們可以看到:Person是個多層次對象,包含多層嵌入屬性對象(multi-layer embeded objects)。如果需要更改Person類型執行個體中的任何字段時,我們可以直接用行令方式(imperative style):

  scala中的case class是一種特殊的對象:由編譯器(compiler)自動生成字段的getter和setter。如下面的例子:

這裡我們可以看到:person是個多層次對象,包含多層嵌入屬性對象(multi-layer embeded objects)。如果需要更改person類型執行個體中的任何字段時,我們可以直接用行令方式(imperative style)

注意:我必須把case class 屬性city的name字段屬性變成var,而且這時peter已經轉變了(mutated)。既然我們是在函數式程式設計中,強調的是純函數代碼,即使用不可變對象(immutable objects),那麼函數式程式設計方式的字段操作又可以怎樣呢?

我們可以使用case class的自帶函數copy來實作字段操作。但是随着嵌入對象層次的增加,将會産生大量的重複代碼。scalaz的lens type class的主要功能之一就可以解決以上問題。我們先來看看scalaz lens的用例:

可以看到:我們用lens.lensu建構了上面那些case class的lens執行個體,然後分别用get,set,mod對case class的字段進行了讀寫操作示範。理論上lens的基本定義大約是這樣的:

實際上lens就是get,set函數的外包(wrapper)。r,f可以分别被了解為記錄record和字段field類型。get和set是lambda表達式,分别代表:給一個record,傳回field結果;給一個record及一個字段值,更新record中這個字段值後傳回新的record。

我們看看在scalaz中是如何定義lens的:scalaz/package.scala

plens是部分作用(partial)lens,與lens不同的是plens的get和set傳回的結果都是option類型的。我們再看看lensfamily的定義:scalaz/lens.scala

如上可以這樣了解lensfamily的類型參數lensfamily[r1,r2,f1,f2],分别代表操作前後record和field的類型。這樣又提供了類型轉換(type transformation)操作,可以概括更多類型的lens。lens包嵌了個store類型,我們看看store的定義:scalaz/storet.scala

及scalaz/package.scala

首先,store就是個函數存儲器。與lens共同使用時它可以存放get和set函數。從store的存取函數可以分析得見:scalaz/storet.scala

我們看見:如果f = id的話,put(a) = run._1(a) >>> run._1 是 a => b,函數輸入a,傳回b,是個setter。pos = run._2 = a,是個getter。但lens的get,set好像是這樣的: get: a => b, set: a => b => a,與store的a,b剛好相反,不過看看scalaz lens的建構函數就明白了:scalaz/lens.scala

run(a) >>> store[b,a], 剛好相反。可以說store的b就是record類型,a就是field類型。是以scalaz版本的lens包嵌的是個store[f,r]。我們可以用pos和put把擷取get和set。看看lensfamily的get和set源代碼:

實際上即使用lens過程中為每個字段定義lens也會涉及到大量的重複代碼。但lens是可組合的(composible),這樣我們可以重複利用最基本細小的lens來組合成更大範圍的lens。scalaz提供了lens組合函數:

我們試着把lens組合起來做個示範:

lens可以被轉換成state:

這裡有一個隐式類型轉換函數可以把lensfamily轉成state。

scalaz提供了許多函數讓我們可以把lens操作結果轉化成state類型:

因為state是個monad,我們可以用for-comprehension來實作行令式程式設計:

這是一段比較典型的行令程式。

上面例子的 += 是numericlens一項操作。numericlens是這樣定義的:

scalar還提供了許多标準資料類型的lens: 

我們下面示範一些用例:

以上是針對獨立的immutable set和immutable map的操作。與上面的numericlens示範一樣,scalaz還提供了針對包嵌在對象内屬性的标準類型操作函數,比如如果上面例子的set和map是case class的字段時該如何操作: 

當然,scalaz提供的還有其它類型的lens,這裡就不一一示範了,具體可以參考源代碼scalaz/lens.scala

從以上讨論我們了解到lens不但解決了多層嵌入屬性操作重複代碼問題,它還可以進行函數組合,實作重複使用基本lens組合擷取各種不同功能的lens。最重要的是lens與state一同使用可以讓我們采用行令程式設計方式來對對象的嵌入屬性進行操作。