Functional programming leads to deep insights into the nature of computation. -- Martin Odersky
形式化
FizzBuzzWhizz
詳細描述請自行查閱相關(guān)資料。此處以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
接下來我將使用Scala
嘗試FizzBuzzWhizz
問題的設(shè)計和實現(xiàn)。
語義模型
從上面的形式化描述,可以很容易地得到FizzBuzzWhizz
問題的語義模型。
Rule: (Int) -> String
Matcher: (Int) -> Boolean
Action: (Int) -> String
其中,Rule
存在三種基本的類型:
Rule ::= atom | allof | anyof
三者之間構(gòu)成了「樹型」結(jié)構(gòu)。
atom: (Matcher, Action) -> String
allof: rule1 && rule2 ...
anyof: rule1 || rule2 ...
測試用例
借助Scala
強大的「類型系統(tǒng)」能力,可拋棄掉很多重復(fù)的「樣板代碼」,使得設(shè)計更加簡單、漂亮。此外,Scala
構(gòu)造DSL
的能力也相當(dāng)值得稱贊,非常直接,簡單。
import org.scalatest._
import prop._
class RuleSpec extends PropSpec with TableDrivenPropertyChecks with Matchers {
val spec = {
val r1_3 = atom(times(3), to("Fizz"))
val r1_5 = atom(times(5), to("Buzz"))
val r1_7 = atom(times(7), to("Whizz"))
val r1 = anyof(r1_3, r1_5, r1_7)
val r2 = anyof(
allof(r1_3, r1_5, r1_7),
allof(r1_3, r1_5),
allof(r1_3, r1_7),
allof(r1_5, r1_7))
val r3 = atom(contains(3), to("Fizz"))
val rd = atom(always(true), nop);
anyof(r3, r2, r1, rd)
}
val specs = Table(
("n", "expect"),
(3, "Fizz"),
(5, "Buzz"),
(7, "Whizz"),
(3 * 5, "FizzBuzz"),
(3 * 7, "FizzWhizz"),
((5 * 7) * 2, "BuzzWhizz"),
(3 * 5 * 7, "FizzBuzzWhizz"),
(13, "Fizz"),
(35, "Fizz"), // 35 > 5*7
(2, "2")
)
property("fizz buzz whizz") {
forAll(specs) { spec(_) should be (_) }
}
}
匹配器:Matcher
Matcher
是一個「一元函數(shù)」,入?yún)?code>Int,返回值為Boolean
,是一種典型的「謂詞」。從OO
的角度看,always
是一種典型的Null Object
。
object Matchers {
type Matcher = Int => Boolean
def times(n: Int): Matcher = _ % n == 0
def contains(n: Int): Matcher = _.toString.contains(n.toString)
def always(bool: Boolean): Matcher = _ => bool
}
執(zhí)行器:Action
Action
也是一個「一元函數(shù)」,入?yún)?code>Int,返回值為String
,其本質(zhì)就是定制常見的map
操作,將定義域映射到值域。
object Actions {
type Action = Int => String
def to(str: String): Action = _ => str
def nop: Action = _.toString
}
規(guī)則:Rule
Composition Everywhere
Rule
是FizzBuzzWhizz
最核心的抽象,也是設(shè)計的靈魂所在。從語義上Rule
分為2
種基本類型,并且兩者之間形成了優(yōu)美的、隱式的「樹型」結(jié)構(gòu),體現(xiàn)了「組合式設(shè)計」的強大威力。
Atomic
Compositions: anyof, allof
Rule
是一個「一元函數(shù)」,入?yún)?code>Int,返回值為String
。其中,def atom(matcher: => Matcher, action: => Action)
的入?yún)⑹褂?code>Pass By Name的惰性求值的特性。
object Rules {
type Rule = (Int) => String
def atom(matcher: => Matcher, action: => Action): Rule =
n => if (matcher(n)) action(n) else ""
def anyof(rules: Rule*): Rule =
n => rules.map(_(n))
.filterNot(_.isEmpty)
.headOption
.getOrElse("")
def allof(rules: Rule*): Rule =
n => rules.foldLeft("") { _ + _(n) }
}
源代碼
- Github: https://github.com/horance-liu/fizz-buzz-whizz
- C++11參考實現(xiàn): https://codingstyle.cn/topics/97
- Java參考實現(xiàn): https://codingstyle.cn/topics/100
- Ruby參考實現(xiàn): https://codingstyle.cn/topics/114