Functional programming leads to deep insights into the nature of computation. -- Martin Odersky
形式化
FizzBuzzWhizz
详细描述请自行查阅相关资料。此处以3, 5, 7
为例,形式化地描述一下问题。
r1
- times(3) -> Fizz
- times(5) -> Buzz
- times(7) -> Whizz
r2
- times(3) && times(5) && times(7) -> FizzBuzzWhizz
- times(3) && times(5) -> FizzBuzz
- times(3) && times(7) -> FizzWhizz
- times(5) && times(7) -> BuzzWhizz
r3
- contains(3) -> Fizz
- the priority of contains(3) is highest
rd
- others -> others
接下来我将使用Go
尝试FizzBuzzWhizz
问题的设计和实现。
语义模型
从上面的形式化描述,可以很容易地得到FizzBuzzWhizz
问题的语义模型。
type matcher func(int) bool
type action func(int) string
type rule func(int) string
其中,Rule
存在三种基本的类型:
rule ::= atom | all | any
三者之间构成了「树型」结构。
atom: (matcher, action) -> string
all: rule1 && rule2 ...
any: rule1 || rule2 ...
匹配器:matcher
matcher
是一个「一元函数」,入参为int
,返回值为bool
,是一种典型的「谓词」。从OO
的角度看,always
是一种典型的Null Object
。
import (
"strconv"
"strings"
)
type matcher func(int) bool
func times(n int) matcher {
return func(m int) bool {
return m % n == 0
}
}
func contains(n int) matcher {
return func(m int) bool {
return strings.Contains(strconv.Itoa(m), strconv.Itoa(n))
}
}
func always() matcher {
return func(int) bool {
return true
}
}
执行器:action
action
也是一个「一元函数」,入参为int
,返回值为string
,其本质就是定制常见的map
操作,将定义域映射到值域。
import "strconv"
type action func(int) string
func to(s string) action {
return func(int) string {
return s
}
}
func nop() action {
return func(m int) string {
return str(m)
}
}
规则:rule
Composition Everywhere
rule
是FizzBuzzWhizz
最核心的抽象,也是设计的灵魂所在。从语义上rule
分为2
种基本类型,并且两者之间形成了优美的、隐式的「树型」结构,体现了「组合式设计」的强大威力。
Atom
Compositions: any, all
rule
是一个「一元函数」,入参为int
,返回值为string
。
type rule func(int) string
func atom(m matcher, a action) rule {
return func(n int) string {
if m(n) {
return a(n)
}
return ""
}
}
func all(rules []rule) rule {
return func(n int) (s string) {
for _, r := range rules {
s += r(n)
}
return s
}
}
func any(rules []rule) rule {
return func(n int) string {
for _, r := range rules {
if s := r(n); len(s) != 0 {
return s
}
}
return ""
}
}
应用程序:main
package main
func spec(n1, n2, n3 int) rule {
rn1 := atom(times(n1), to("Fizz"))
rn2 := atom(times(n2), to("Bizz"))
rn3 := atom(times(n3), to("Whizz"))
r3 := atom(contains(n1), to("Fizz"))
r2 := all([]rule{rn1, rn2, rn3})
rd := atom(always(), nop())
return any([]rule{r3, r2, rd})
}
func start(n int, saying rule) {
for i := 1; i <= n; i++ {
fmt.Printf("%d -> %s\n", i, saying(i))
}
}
func main() {
start(100, spec(3, 5, 7))
}