TDD The Coding Kata: FizzBuzzWhizz in Go

horance · December 31, 2020 · 41 hits

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))
}

源代码

「软件匠艺社区」旨在传播匠艺精神,通过分享好的「工作方式」,让帮助程序员更加快乐高效地编程!

No Reply at the moment.
You need to Sign in before reply, if you don't have an account, please Sign up first.