The Coding Kata: FizzBuzzWhizz in Go

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

ruleFizzBuzzWhizz最核心的抽象,也是设计的灵魂所在。从语义上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))
}

源代码

共 0 个回复