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
接下來我將使用Scala
嘗試FizzBuzzWhizz
問題的設計和實現。
語義模型
從上面的形式化描述,可以很容易地得到FizzBuzzWhizz
問題的語義模型。
Rule: (Int) -> String
Matcher: (Int) -> Boolean
Action: (Int) -> String
其中,Rule
存在三種基本的類型:
Rule ::= atom | allof | anyof
三者之間構成了「樹型」結構。
atom: (Matcher, Action) -> String
allof: rule1 && rule2 ...
anyof: rule1 || rule2 ...
測試用例
借助Java8
增強了的「函數式」能力,可拋棄掉很多重復的「樣板代碼」,使得設計更加簡單、漂亮。此外,Java8
構造DSL
的能力也相當值得稱贊,非常直接,簡單。測試用例此處選擇Spock
(基于Groovy
語言),可提高用例的可讀性和可維護性。
class RuleSpec extends Specification {
private static def spec() {
Rule r1_3 = atom(times(3), to("Fizz"))
Rule r1_5 = atom(times(5), to("Buzz"))
Rule r1_7 = atom(times(7), to("Whizz"))
Rule r1 = anyof(r1_3, r1_5, r1_7)
Rule r2 = anyof(allof(r1_3, r1_5, r1_7),
allof(r1_3, r1_5),
allof(r1_3, r1_7),
allof(r1_5, r1_7))
Rule r3 = atom(contains(3), to("Fizz"))
Rule rd = atom(always(true), nop())
anyof(r3, r2, r1, rd)
}
def "fizz buzz whizz"() {
expect:
spec().apply(n) == expect
where:
n | expect
3 | "Fizz"
5 | "Buzz"
7 | "Whizz"
3 * 5 * 7 | "FizzBuzzWhizz"
3 * 5 | "FizzBuzz"
3 * 7 | "FizzWhizz"
(5 * 7) * 2 | "BuzzWhizz"
13 | "Fizz"
35 /* 5*7 */ | "Fizz" /* not "BuzzWhizz" */
2 | "2"
}
}
匹配器:Matcher
Matcher
是一個「一元函數」,入參為Int
,返回值為Boolean
,是一種典型的「謂詞」。從OO
的角度看,always
是一種典型的Null Object
。
import static java.lang.String.valueOf;
@FunctionalInterface
public interface Matcher {
boolean matches(int n);
static Matcher times(int n) {
return x -> x % n == 0;
}
static Matcher contains(int n) {
return x -> valueOf(x).contains(valueOf(n));
}
static Matcher always(boolean bool) {
return n -> bool;
}
}
執行器:Action
Action
也是一個「一元函數」,入參為Int
,返回值為String
,其本質就是定制常見的map
操作,將定義域映射到值域。
@FunctionalInterface
public interface Action {
String to(int n);
static Action to(String str) {
return n -> str;
}
static Action nop() {
return n -> String.valueOf(n);
}
}
規則:Rule
Composition Everywhere
Rule
是FizzBuzzWhizz
最核心的抽象,也是設計的靈魂所在。從語義上Rule
分為2
種基本類型,并且兩者之間形成了優美的、隱式的「樹型」結構,體現了「組合式設計」的強大威力。
- Atom
Compositions: anyof, allof
Rule
是一個「二元函數」,入參為(Int)
,返回值為String
。
@FunctionalInterface
public interface Rule {
String apply(int n);
}
Rules
為一個工廠類,用于生產各種Rule
。其中,allof, anyof
使用了Java8 Stream
的API
。
public final class Rules {
public static Rule atom(Matcher matcher, Action action) {
return n -> matcher.matches(n) ? action.to(n) : "";
}
public static Rule anyof(Rule... rules) {
return n ->sstream(n, rules)
.filter(s -> !s.isEmpty())
.findFirst()
.orElse("");
}
public static Rule allof(Rule... rules) {
return n -> sstream(n, rules)
.collect(joining());
}
private static Stream<String> sstream(int n, Rule[] rules) {
return Arrays.stream(rules)
.map(r -> r.apply(n));
}
private Rules() {
}
}
源代碼
- Github: https://github.com/horance-liu/fizz-buzz-whizz
- C++11參考實現: https://codingstyle.cn/topics/97
- Scala參考實現: https://codingstyle.cn/topics/99
- Ruby參考實現: https://codingstyle.cn/topics/114