Functional Programming
啥是FP?
FP(Functional Programming)是一种程序风格。
- 程序可独立运行的最小单位为函数(而不是Class等)。(Function is First-class)
- 幂等性。相同的参数执行多次,执行结果与首次执行相同。(Idempotency)
- 外部更改免疫。在程序外部无法更改程序的状态。(Immutable Data Structures)
FP in Groovy - Immutable
-
使用 final 关键字定义个别项目 immutable
class Student { final String name Student(String name) { this.name = name } } def p = new Student("Peter") // 当注释掉显示的Contructor,编译通过。也会产生如下错误: // groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: name for class: Student //def t = new Student([name:"Tom"]) println p.name // println t.name // 也会产生如下错误: // groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: name for class: Student //p.name="tom"
-
使用 @Immutable
可添加@Immutable, 来包含Class所有的项目不可更改,例如:
import groovy.transform.*
@Immutable
class Student {
String name
List<String> aliaNames
}
def p = new Student("Peter", ["Arthur"])
println p.name
println p.aliaNames
// 以下代码会产生错误:
// groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: aliaNames for class: Student
// p.aliaNames = []
// p.aliaNames += "Tom"
实际生成的代码如下:
@groovy.transform.ToString(includeSuperProperties = true, cache = true)
@groovy.transform.EqualsAndHashCode(cache = true)
@groovy.transform.ImmutableBase
@groovy.transform.ImmutableOptions
@groovy.transform.PropertyOptions(propertyHandler = groovy.transform.options.ImmutablePropertyHandler)
@groovy.transform.TupleConstructor(defaults = false)
@groovy.transform.MapConstructor(noArg = true, includeSuperProperties = true, includeFields = true)
@groovy.transform.KnownImmutable
public class Student extends java.lang.Object {
private java.lang.String name
private java.util.List<String> aliaNames
}
- 使用第三方的Immutable实现。
使用 @Immutable(knownImmutableClasses = []) 可以使用第三方的Immutable实现。例如:import groovy.transform.* import com.google.common.collect.ImmutableList @Grab("com.google.guava:guava:28.2-jre") @Immutable(knownImmutableClasses = [ImmutableList]) class Student { String name ImmutableList<String> aliaNames } def p = new Student("Peter", ImmutableList.of("Arthur")) println p.name println p.aliaNames // 以下代码会产生错误: // groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: aliaNames for class: Student //p.aliaNames = [] //p.aliaNames += "Tom"
FP in Groovy - Groovy Curry(Groovy 咖喱?)
Groovy Curry又是Groovy比较强大的功能。能够设置Closure参数的初值,从而得到新的Closure(Function), 先看一个比较简单的例子:
def feedingPets = { pet, food -> println "Feeding ${food} to ${pet}"}
feedingPets "fish", "cat"
feedingPets "meat", "cat"
def feedingCat = feedingPets.curry("cat")
feedingCat "fish"
feedingCat "meat"
执行结果如下:
Feeding cat to fish
Feeding cat to meat
Feeding fish to cat
Feeding meat to cat
再来看一个比较函数组合的例子:
def putAnimalToFreezer = { putAnimal ->
println "Open the freezer"
putAnimal()
println "Close the freezer"
}
def putElephantsToFrezzer = putAnimalToFreezer.curry{
println "Go to Thailand"
println "Buy an elephant"
println "Fly back with the elephant"
println "Put the elephant into the freezer"
}
putElephantsToFrezzer()
执行结果如下:
Open the freezer
Go to Thailand
Buy an elephants
Flying back with the elephant
Put the elephant into the freezer
Close the freezer
Method Handles (&MethodName)
可将Class’s Method 作为Closure, 具有Closure的特性(例如可以:curry)
class HunterSchool {
public void huntTemplate(String weapon, String target){
println "Use ${weapon} to hunt ${target}"
}
public Closure trainToUseWeapon(String weapon) {
return this.&huntTemplate.curry(weapon)
}
}
def huntByPistol = new HunterSchool().trainToUseWeapon("Pistol")
huntByPistol("Beer")
执行结果如下:
Use Pistol to hunt Beer
Tail Recursion
当递归的层级很深,调用链中的函数无法返回,会造成StackOverFlowError。
而Groovy提供了 @TailRecursive Annotation可以将递归函数转为非递归的函数。
例如:
import groovy.transform.*
@TailRecursive
long totalPopulation(list, total = 0) {
if (list.size() == 0)
total
else
totalPopulation(list.tail(), total + list.first().population)
}
@Canonical class City {int population}
def cities = (10..1000).collect{new City(it)}
totalPopulation(cities)
执行结果如下:
Result: 500455
生成的代码(AST重写之后)如下:
import groovy.transform.*
public class script1599199449316 extends groovy.lang.Script {
public script1599199449316() {
}
public script1599199449316(groovy.lang.Binding context) {
super(context)
}
public static void main(java.lang.String[] args) {
org.codehaus.groovy.runtime.InvokerHelper.runScript(script1599199449316, args)
}
public java.lang.Object run() {
java.lang.Object cities = (10..1000).collect({
new City(it)
})
this.totalPopulation(cities)
}
@groovy.transform.TailRecursive
public long totalPopulation(java.lang.Object list, java.lang.Object total = 0) {
java.lang.Object _total_ = total
java.lang.Object _list_ = list
while (true) {
_RECUR_HERE_:
try {
if (_list_.size() == 0) {
return _total_
} else {
java.lang.Object __list__ = _list_
java.lang.Object __total__ = _total_
_list_ = __list__.tail()
_total_ = __total__ + __list__.first().population
continue _RECUR_HERE_
}
}
catch (org.codehaus.groovy.transform.tailrec.GotoRecurHereException ignore) {
continue _RECUR_HERE_
}
finally {
}
}
}
}
import groovy.transform.*
@groovy.transform.ToString
@groovy.transform.TupleConstructor
@groovy.transform.EqualsAndHashCode
public class City extends java.lang.Object {
private int population
}
疑问
- Groovy Curry美中不足:,只能按照Closure的参数顺序,依次设定默认值。