Presented at Foo Café (2019-03-21)
Video at https://www.youtube.com/watch?v=tLSKLLxrZyY
Programmers use coding katas to kick the tyres of their programming languages, paradigms and practices. Typically anchored in a TDD cycle, katas are simple problems that give programmers the opportunity to exercise deliberate practice and explore different approaches, whether programming style, pair programming or test-first programming.
But the simplicity can be deceptive, with many programmers tiring of these katas too soon, missing out on some of the more mind-bending and paradigm-expanding opportunities on offer.
This session will pick on a couple of katas and dig deeper into TDD, lambdas, language(s), (dys)functional programming and Alcubierre drive. It will present code in a variety of languages, highlight the weaknesses of some common mantras, play around with ideas — and blend code, humour and general nerdiness to be both an enjoyable and educational session.
17. You do deliberate practice to improve
your ability to perform a task. It’s about
skill and technique.
Deliberate practice means repetition.
You do deliberate practice to master the
task, not to complete the task.
Jon Jagger
Do Lots of Deliberate Practice
18. Rien n’est plus dangereux
qu’une idée, quand on n’a
qu’une idée.
Émile-Auguste Chartier
19. Nothing is more dangerous
than an idea, when you
have only one idea.
Émile-Auguste Chartier
33. Fizz buzz is a group word
game for children to teach
them about division.
http://en.wikipedia.org/wiki/Fizz_buzz
34. Players generally sit in a circle. The player
designated to go first says the number "1", and
each player thenceforth counts one number in
turn. However, any number divisible by three is
replaced by the word fizz and any divisible by five
by the word buzz. Numbers divisible by both
become fizz buzz. A player who hesitates or
makes a mistake is eliminated from the game.
http://en.wikipedia.org/wiki/Fizz_buzz
35. Players generally sit in a circle. The player
designated to go first says the number "1", and
each player thenceforth counts one number in
turn. However, any number divisible by three is
replaced by the word fizz and any divisible by five
by the word buzz. Numbers divisible by both
become fizz buzz. A player who hesitates or
makes a mistake is eliminated from the game.
http://en.wikipedia.org/wiki/Fizz_buzz
36. Players generally sit in a circle. The player
designated to go first says the number "1", and
each player thenceforth counts one number in
turn. However, any number divisible by three is
replaced by the word fizz and any divisible by five
by the word buzz. Numbers divisible by both
become fizz buzz. A player who hesitates or
makes a mistake is eliminated from the game.
http://en.wikipedia.org/wiki/Fizz_buzz
37. Adults may play Fizz buzz as a
drinking game, where making
a mistake leads to the player
having to make a drinking-
related forfeit.
http://en.wikipedia.org/wiki/Fizz_buzz
[citation needed]
38. Fizz buzz has been used as an
interview screening device for
computer programmers.
http://en.wikipedia.org/wiki/Fizz_buzz
39. FizzBuzz was invented to avoid
the awkwardness of realising
that nobody in the room can
binary search an array.
https://twitter.com/richardadalton/status/591534529086693376
47. I have yet to see any problem,
however complicated, which,
when you looked at it in the
right way, did not become still
more complicated.
Anderson's Law
48. public class FizzBuzzCalculator
{
private static final String FIZZ = "Fizz";
private static final String BUZZ = "Buzz";
private static final String FIZZBUZZ = "FizzBuzz";
private int fizzMultipleValue;
private int buzzMultipleValue;
private int fizzBuzzMultipleValue;
public FizzBuzzCalculator(int fizzMultipleValue, int buzzMultipleValue)
{
this.fizzMultipleValue = fizzMultipleValue;
this.buzzMultipleValue = buzzMultipleValue;
calculateFizzBuzzMultipleValue();
}
private void calculateFizzBuzzMultipleValue()
{
fizzBuzzMultipleValue = fizzMultipleValue * buzzMultipleValue;
}
public int getFizzMultipleValue()
{
return fizzMultipleValue;
}
public void setFizzMultipleValue(int fizzMultipleValue)
{
this.fizzMultipleValue = fizzMultipleValue;
calculateFizzBuzzMultipleValue();
}
public int getBuzzMultipleValue()
{
return buzzMultipleValue;
}
public void setBuzzMultipleValue(int buzzMultipleValue)
{
this.buzzMultipleValue = buzzMultipleValue;
calculateFizzBuzzMultipleValue();
}
public String returnFizzBuzzOrNumber(int n)
{
if (isFizzBuzz(n))
return FIZZBUZZ;
if (isFizz(n))
return FIZZ;
if (isBuzz(n))
return BUZZ;
return new Integer(n).toString();
}
private static boolean isFizz(int n)
{
return n % fizzMultipleValue == 0;
}
private static boolean isBuzz(int n)
{
return n % buzzMultipleValue == 0;
}
private static boolean isFizzBuzz(int n)
{
return n % fizzBuzzMultipleValue == 0;
}
}
49.
50.
51.
52.
53.
54. def fizzbuzz(n)
{
result = ''
if (n % 3 == 0)
result += 'Fizz'
if (n % 5 == 0)
result += 'Buzz'
if (!result)
result += n
result
}
55. def fizzbuzz(n)
{
if (n % 15 == 0)
'FizzBuzz'
else if (n % 3 == 0)
'Fizz'
else if (n % 5 == 0)
'Buzz'
else
n.toString()
}
56. def fizzbuzz(n)
{
n % 15 == 0 ? 'FizzBuzz' :
n % 3 == 0 ? 'Fizz' :
n % 5 == 0 ? 'Buzz' :
n.toString()
}
57. def fizzbuzz(n)
{
n in (0..100).step(15) ? 'FizzBuzz' :
n in (0..100).step(3) ? 'Fizz' :
n in (0..100).step(5) ? 'Buzz' :
n.toString()
}
63. We instituted a rigorous regression
test for all of the features of AWK. Any
of the three of us who put in a new
feature into the language [...], first
had to write a test for the new feature.
Alfred Aho
http://www.computerworld.com.au/article/216844/a-z_programming_languages_awk/
64. Red
Write a failing test for
a new feature
Green
Write enough code to
pass the test
Refactor
Refine code and tests
Test-First Cycle
65. Red
Write a failing test for
a new feature
Green
Write enough code to
pass the test
Test-First Dumbed Down
68. assert fizzbuzz(1) == '1'
assert fizzbuzz(2) == '2'
def fizzbuzz(n):
if n == 1: return '1'
return '2'
69. assert fizzbuzz(1) == '1'
assert fizzbuzz(2) == '2'
assert fizzbuzz(3) == 'Fizz'
def fizzbuzz(n):
if n == 1: return '1'
if n == 2: return '2'
return 'Fizz'
70. assert fizzbuzz(1) == '1'
assert fizzbuzz(2) == '2'
assert fizzbuzz(3) == 'Fizz'
assert fizzbuzz(4) == '4'
assert fizzbuzz(5) == 'Buzz'
assert fizzbuzz(6) == 'Fizz'
assert fizzbuzz(7) == '7'
assert fizzbuzz(8) == '8'
assert fizzbuzz(9) == 'Fizz'
assert fizzbuzz(10) == 'Buzz'
assert fizzbuzz(11) == '11'
assert fizzbuzz(12) == 'Fizz'
assert fizzbuzz(13) == '13'
assert fizzbuzz(14) == '14'
assert fizzbuzz(15) == 'FizzBuzz'
def fizzbuzz(n):
if n == 1: return '1'
if n == 2: return '2'
if n == 3: return 'Fizz'
if n == 4: return '4'
if n == 5: return 'Buzz'
if n == 6: return 'Fizz'
if n == 7: return '7'
if n == 8: return '8'
if n == 9: return 'Fizz'
if n == 10: return 'Buzz'
if n == 11: return '11'
if n == 12: return 'Fizz'
if n == 13: return '13'
if n == 14: return '14'
return 'FizzBuzz'
71. assert fizzbuzz(1) == '1'
assert fizzbuzz(2) == '2'
assert fizzbuzz(3) == 'Fizz'
assert fizzbuzz(4) == '4'
assert fizzbuzz(5) == 'Buzz'
assert fizzbuzz(6) == 'Fizz'
assert fizzbuzz(7) == '7'
assert fizzbuzz(8) == '8'
assert fizzbuzz(9) == 'Fizz'
assert fizzbuzz(10) == 'Buzz'
assert fizzbuzz(11) == '11'
assert fizzbuzz(12) == 'Fizz'
assert fizzbuzz(13) == '13'
assert fizzbuzz(14) == '14'
assert fizzbuzz(15) == 'FizzBuzz'
...
assert fizzbuzz(98) == '98'
assert fizzbuzz(99) == 'Fizz'
assert fizzbuzz(100) == 'Buzz'
def fizzbuzz(n):
if n == 1: return '1'
if n == 2: return '2'
if n == 3: return 'Fizz'
if n == 4: return '4'
if n == 5: return 'Buzz'
if n == 6: return 'Fizz'
if n == 7: return '7'
if n == 8: return '8'
if n == 9: return 'Fizz'
if n == 10: return 'Buzz'
if n == 11: return '11'
if n == 12: return 'Fizz'
if n == 13: return '13'
if n == 14: return '14'
if n == 15: return 'FizzBuzz'
...
if n == 98: return '98'
if n == 99: return 'Fizz'
return 'Buzz'
72. def fizzbuzz(n):
if n == 1: return '1'
if n == 2: return '2'
if n == 3: return 'Fizz'
if n == 4: return '4'
if n == 5: return 'Buzz'
if n == 6: return 'Fizz'
if n == 7: return '7'
if n == 8: return '8'
if n == 9: return 'Fizz'
if n == 10: return 'Buzz'
if n == 11: return '11'
if n == 12: return 'Fizz'
if n == 13: return '13'
if n == 14: return '14'
if n == 15: return 'FizzBuzz'
...
if n == 98: return '98'
if n == 99: return 'Fizz'
if n == 100: return 'Buzz'
76. Red
Write a failing test for
a new feature
Green
Write enough code to
pass the test
Refactor
Refine code and tests
Test-First Cycle
77. Plan
Establish hypothesis,
goal or work tasks
Do
Carry out plan
Study
Review what has
been done against
plan (a.k.a. Check)
Act
Revise approach
or artefacts based
on study
Deming/Shewhart Cycle
78. Write
Create or extend a test
case for new
behaviour — as it's
new, the test fails
Reify
Implement so that
the test passes
Reflect
Is there something in
the code or tests that
could be improved?
Refactor
Make it so!
Test-First Cycle
79. actual = [fizzbuzz(n) for n in range(1, 101)]
truths = [
every result is 'Fizz', 'Buzz', 'FizzBuzz' or a decimal string,
every decimal result corresponds to its ordinal position,
every third result contains 'Fizz',
every fifth result contains 'Buzz',
every fifteenth result is 'FizzBuzz',
the ordinal position of every 'Fizz' result is divisible by 3,
the ordinal position of every 'Buzz' result is divisible by 5,
the ordinal position of every 'FizzBuzz' result is divisible by 15
]
assert all(truths)
80. actual = [fizzbuzz(n) for n in range(1, 101)]
truths = [
all(a in {'Fizz', 'Buzz', 'FizzBuzz'} or a.isdecimal() for a in actual),
all(int(a) == n for n, a in enumerate(actual, 1) if a.isdecimal()),
all('Fizz' in a for a in actual[2::3]),
all('Buzz' in a for a in actual[4::5]),
all(a == 'FizzBuzz' for a in actual[14::15]),
all(n % 3 == 0 for n, a in enumerate(actual, 1) if a == 'Fizz'),
all(n % 5 == 0 for n, a in enumerate(actual, 1) if a == 'Buzz'),
all(n % 15 == 0 for n, a in enumerate(actual, 1) if a == 'FizzBuzz')
]
assert all(truths)
81.
82.
83.
84. A work of art is the
unique result of a
unique temperament.
Oscar Wilde
90. bi-quinary coded decimal, noun
▪ A system of representing numbers based on counting in fives,
with an additional indicator to show whether the count is in the
first or second half of the decimal range, i.e., whether the number
represented is in the range 0–4 or 5–9 (or in the range 1–5 or 6–10).
▪ This system is found in many abacus systems, with paired
columns of counters (normally aligned) representing each bi-
quinary range.
▪ The Roman numeral system is also a form of bi-quinary coded
decimal.
103. proc roman numerals = (int year) string:
if year = 1000 then “M”
elif year = 500 then “D”
elif year = 100 then “C”
elif year = 50 then “L”
elif year = 10 then “X”
elif year = 5 then “V”
else “I”
fi;
105. proc roman numerals = (int year) string:
begin
string result := "";
for entry from lwb mapping to upb mapping do
if value of mapping[entry] = year then
result := letters of mapping[entry]
fi
od;
result
end;
106. proc roman numerals = (int year) string:
(
string result := "";
for entry from lwb mapping to upb mapping do
if value of mapping[entry] = year then
result := letters of mapping[entry]
fi
od;
result
);
109. [] proposition roman numerals spec =
(
("Decimal positions correspond to numerals",
((1, "I"), (10, "X"), (100, "C"), (1000, "M"))),
("Quinary intervals correspond to numerals",
((5, "V"), (50, "L"), (500, "D")))
);
test (roman numerals, roman numerals spec)
110. mode test = struct (int input, string expected);
mode proposition = struct (string name, flex [1:0] test tests);
111. proc test = (proc (int) string function, [] proposition spec) void:
for entry from lwb spec to upb spec do
print (name of spec[entry]);
string report := "", separator := " failed:";
[] test tests = tests of spec[entry];
for test from lwb tests to upb tests do
int input = input of tests[test];
string expected = expected of tests[test];
string actual = function (input);
if actual /= expected then
report +:=
separator + " for " + whole (input, 0) +
" expected " + expected + " but was " + actual
separator := “,”
fi
od;
print (if report = "" then (new line) else (new line, report, new line) fi)
od;
112. [] proposition roman numerals spec =
(
("Decimal positions correspond to numerals",
((1, "I"), (10, "X"), (100, "C"), (1000, "M"))),
("Quinary intervals correspond to numerals",
((5, "V"), (50, "L"), (500, "D"))),
("Multiples of decimals are additive",
((2, "II"), (30, "XXX"), (200, "CC"), (4000, "MMMM")))
);
113. proc roman numerals = (int year) string:
(
string result := "";
int value = year;
for entry from lwb mapping to upb mapping do
if value mod value of mapping[entry] = 0 then
while value > 0 do
result +:= letters of mapping[entry];
value -:= value of mapping[entry]
od
fi
od;
result
);
114. [] proposition roman numerals spec =
(
("Decimal positions correspond to numerals",
((1, "I"), (10, "X"), (100, "C"), (1000, "M"))),
("Quinary intervals correspond to numerals",
((5, "V"), (50, "L"), (500, "D"))),
("Multiples of decimals are additive",
((2, "II"), (30, "XXX"), (200, "CC"), (4000, "MMMM"))),
("Non-multiples of decimals are additive",
((6, "VI"), (23, "XXIII"), (273, "CCLXXIII"), (1500, "MD")))
);
115. proc roman numerals = (int year) string:
(
string result := "";
int value = year;
for entry from lwb mapping to upb mapping do
while value >= value of mapping[entry] do
result +:= letters of mapping[entry];
value -:= value of mapping[entry]
od
od;
result
);
116. [] proposition roman numerals spec =
(
("Decimal positions correspond to numerals",
((1, "I"), (10, "X"), (100, "C"), (1000, "M"))),
("Quinary intervals correspond to numerals",
((5, "V"), (50, "L"), (500, "D"))),
("Multiples of decimals are additive",
((2, "II"), (30, "XXX"), (200, "CC"), (4000, "MMMM"))),
("Non-multiples of decimals are additive",
((6, "VI"), (23, "XXIII"), (273, "CCLXXIII"), (1500, "MD"))),
("Numeral predecessors are subtractive",
((4, "IV"), (9, "IX"), (40, "XL"), (90, "XC"), (400, "CD"), (900, "CM")))
);
118. [] proposition roman numerals spec =
(
("Decimal positions correspond to numerals",
((1, "I"), (10, "X"), (100, "C"), (1000, "M"))),
("Quinary intervals correspond to numerals",
((5, "V"), (50, "L"), (500, "D"))),
("Multiples of decimals are additive",
((2, "II"), (30, "XXX"), (200, "CC"), (4000, "MMMM"))),
("Non-multiples of decimals are additive",
((6, "VI"), (23, "XXIII"), (273, "CCLXXIII"), (1500, "MD"))),
("Numeral predecessors are subtractive",
((4, "IV"), (9, "IX"), (40, "XL"), (90, "XC"), (400, "CD"), (900, "CM"))),
("Subtractive predecessors are additive",
((14, "XIV"), (42, "XLII"), (1968, "MCMLXVIII")))
);
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132. We could, of course, use any notation
we want; do not laugh at notations;
invent them, they are powerful. In
fact, mathematics is, to a large extent,
invention of better notations.
Richard Feynman