1. 程式人生 > >Groovy 1.8 新特性: 增強的DSL和閉包特性

Groovy 1.8 新特性: 增強的DSL和閉包特性

雖然 JDK7 千呼萬喚不出來,Groovy 還是如期升級到 1.8 版本。大致讀了下 Release Notes,改動不大(大了還得了),但是亮點還是有的。

加強的 DSL 特性:

把 Scala 和舊版的 Groovy 進行比較,會發現 Groovy 被迫寫了很多點和括號,而 Scala 則看起來更像是自然的英語。前幾天我還絮叨過這個問題,但現在有所不同了

   1: turn left then right
   2: paint wall with red, green and yellow

印象深刻!

在新版本中,a b c d 等同於 a(b).c(d),由此,可以寫出更自然的 DSL。(支援多引數、閉包和命名引數)

PS: 對多數人來說,寫 DSL 的情況不多,但是多半接觸過 Gradle、Grails 等工具,多少會有了解。要做出好的設計,必須對 command chains 的模式(誰知道有沒有現有的模式對應?我記得以前用過一種不斷返回自身的連續介面,中文叫啥來著,和這個類似,但要簡單很多)很熟悉。即使不寫 DSL,在指令碼中應用以上的特性也是很能改善可讀性的。

效能提升:

1.8.x 將對基礎型別和方法呼叫做大量的優化(目前 int 的四則運算和比較已經被優化了)。Groovy 由於其動態模型必須在計算前作大量的檢查,在新版本中,如果符合一定的前提條件,則這些檢查將被略過,從而加快執行速度。

PS II. 如果細讀文件會發現這些優化目前僅針對相當受限的條件,比如提到的整數運算,如果你計算 int + int,那麼就可以得到優化的速度,可如果計算 int + int + long 則立刻回到原有的模式。但需要強調的是,程式碼原來怎麼寫,現在還怎麼寫,不要去迎合這些優化。否則,1.9 版本一出,難道重寫所有程式碼?

PS III. 而且我對 Groovy 的效能要求也不高,至少以前單位裡的那些小白鼠也沒有因為我用了 Groovy 就來投訴我的。就算萬一遇到瓶頸了,還可以用 Groovy++ 來頂一下。以前沒有吧 Groovy++ 作為首選,但現在 Groovy 的 Windows 安裝包裡已經預設包含了 Groovy++,官方的態度也算是不言自明瞭吧。

GPars

說實在,這個我真不太關心。主要是覺得要是這玩意管用,那我的 Scala 不是白學了麼,老子又不是搞科研的Smile。話雖如此,多核時代,多點選擇還是好的。

增強的閉包!!

這個喜歡。

首先是閉包現在可以作為註釋的引數了:(例程我改過了,原來的程式碼能說明意思,但本身有問題)

   1: import java.lang.annotation.*
   2:  
   3: @Retention(RetentionPolicy.RUNTIME)
   4: @interface Invariant {
   5:     Class value() //此處value()將返回註釋引數的class
   6: }
   7:  
   8: @Invariant({ number >= 0 }) //在新版本中,閉包可以作為註釋的引數
   9: class Distance {
  10:     def number, unit
  11: }
  12:  
  13: //以下我對Release Notes中的範例做了修改,原版的程式碼顯然是……錯的
  14: def anno = Distance.getAnnotation(Invariant) //這裡和Java的用法相同
  15:  
  16: Object[] args = [[number: 12], [unit: 'km']] //這是針對下一行的newInstance函式(Groovy的Class類方法)
  17: def check = anno.value().newInstance(args) //注意這裡check是Distance下的閉包,而不是Distance的例項
  18: println check.class
  19:  
  20: assert check()

這二十行程式碼可能看起來沒有什麼用,但對於框架設計者來說則提供了很多有趣的可能。(比如“按合約設計”)

然後是閉包之間的組合(這個帥呆了)

   1: triple = { it * 3 }
   2: square = { it ** 2 }
   3:  
   4: tripleBeforeSquare = triple >> square
   5: assert tripleBeforeSquare(1) == 9 // square(triple(1))
   6:  
   7: tripleAfterSquare = triple << square
   8: assert tripleAfterSquare(1) == 3 // triple(square(1))

Trampoline!

   1: /*
   2: factorial = {
   3:     it > 1 ? factorial(it - 1) * it : 1
   4: }
   5: 
   6: factorial 1000
   7: */
   8:  
   9: rec = { n, f = 1G -> // 1G的寫法表示BigInteger
  10:     n > 1 ? rec.trampoline(n - 1, n * f) : f
  11: }
  12:  
  13: factorial = rec.trampoline()
  14:  
  15: factorial 1000 toString() size() // 2560位的大數,你不會想把它打印出來的

在註釋掉的部分,我們看到了一箇舊式的階乘閉包。這個閉包不太管用,因為過深的遞迴將造成溢位錯誤。通過新的 Trampoline 閉包,Groovy “不太優雅”的解決了這個問題。

PS IV: 之所以說不太優雅,是和 Scala 的尾遞迴相比較而言的。但毫無疑問,到目前為止,這個新特性可能是以後最為常用的特性之一。

閉包快取

新版本中,閉包的結果可以進行快取(假設給定相同的引數必定返回相同的結果)

   1: cls = { ... do something ... }.memoize()
   2: // .memoizeAtMost(max) 最多快取max個結果
   3: // .memoizeAtLeast(min) 至少快取min個結果
   4: // .memoizeBetween(min, max) 快取min到max個結果

出人意料的新功能!以前總是用 map 來實現簡單的快取,現在直接用閉包就可以做到。當然,危險性則是這個閉包是否總是針對相同的引數返回相同的結果。(畢竟 Groovy 不是純粹的函式風格)

Currying

又一個偽裝的函式風格!(注意,這一特性也被加入了 1.7 的最新版本)

   1: divide = { a, b -> a / b }
   2:  
   3: // 反向的 currying:被替換的是b(右側)而不是a
   4: halve = divide.rcurry(2)
   5: assert halve(4) == 2
   6:  
   7: // curry化索引為n的引數
   8: halve2 = divide.ncurry(1, 2)
   9: assert halve2(4) == 2