Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
declarative thinking,
declarative practice
@KevlinHenney
Computer Science in the 1960s to 80s
spent a lot of effort making languages
which were as powerful as possible.
Nowadays w...
The reason for this is that the less
powerful the language, the more you can
do with the data stored in that language.
If ...
The makefile language is similar
to declarative programming.
This class of language, in which
necessary end conditions are...
Representation
Is the Essence of
Programming
Show me your flowcharts and
conceal your tables, and I
shall continue to be mystified.
Show me your tables, and I
won’t us...
Excel is the world's
most popular
functional language.
Simon Peyton-Jones
try {
Integer.parseInt(time.substring(0, 2));
}
catch (Exception x) {
return false;
}
if (Integer.parseInt(time.substring(...
Burk Hufnagel
"Put the Mouse Down and Step Away from the Keyboard"
public static boolean validateTime(String time) {
retur...
Many programming languages support
programming in both functional and
imperative style but the syntax and
facilities of a ...
intension, n. (Logic)
 the set of characteristics or properties by which
the referent or referents of a given expression ...
{ x2 | x  , x ≥ 1  x ≤ 100 }
select from where
A list comprehension is a syntactic
construct available in some programming
languages for creating a list based on
existin...
{ x2 | x  , x ≥ 1 }
Lazy
evaluation
https://twitter.com/richardadalton/status/591534529086693376
def fizzbuzz(n):
result = ''
if n % 3 == 0:
result += 'Fizz'
if n % 5 == 0:
result += 'Buzz'
if not result:
result = str(n...
def fizzbuzz(n):
if n % 15 == 0:
return 'FizzBuzz'
elif n % 3 == 0:
return 'Fizz'
elif n % 5 == 0:
return 'Buzz'
else:
ret...
def fizzbuzz(n):
return (
'FizzBuzz' if n % 15 == 0 else
'Fizz' if n % 3 == 0 else
'Buzz' if n % 5 == 0 else
str(n))
def fizzbuzz(n):
return (
'FizzBuzz' if n in range(0, 101, 15) else
'Fizz' if n in range(0, 101, 3) else
'Buzz' if n in ra...
fizzes = [''] + ([''] * 2 + ['Fizz']) * 33 + ['']
buzzes = [''] + ([''] * 4 + ['Buzz']) * 20
numbers = list(map(str, range...
actual = [fizzbuzz(n) for n in range(1, 101)]
truths = [
every result is 'Fizz', 'Buzz', 'FizzBuzz' or a decimal string,
e...
actual = [fizzbuzz(n) for n in range(1, 101)]
truths = [
all(a in {'Fizz', 'Buzz', 'FizzBuzz'} or a.isdecimal() for a in a...
https://twitter.com/wm/status/7206700352
/ WordFriday
bi-quinary coded decimal, noun
 A system of representing numbers based on
counting in fives, with an additional indicator...
// Get the unique surnames in uppercase of the
// first 15 book authors that are 50 years old
// or older?
library.stream(...
// Get the first 15 unique surnames in
// uppercase of the book authors that are 50
// years old or older.
library.stream(...
// Get the unique surnames in uppercase of the
// first 15 book authors that are 50 years old
// or older.
library.stream(...
Simple filters that can be arbitrarily
chained are more easily re-used,
and more robust, than almost any
other kind of cod...
/ WordFriday
paraskevidekatriaphobia, noun
 The superstitious fear of Friday 13th.
 Contrary to popular myth, this superstition is
re...
function NextFriday13thAfter($from) {
(1..500) |
%{ $from.AddDays($_) } |
?{ $_.Day -eq 13} |
?{ $_.DayOfWeek -eq [DayOfWe...
Stack
Stack
{push, pop, depth, top}
Stack[T]
{
push(T),
pop(),
depth() : Integer,
top() : T
}
An interface is a contract to deliver
a certain amount of service.
Clients of the interface depend on
the contract, which ...
Stack[T]
{
push(T item),
pop(),
depth() : Integer,
top() : T
}
given:
before = depth()
postcondition:
depth() = before + 1...
alphabet(Stack) =
{push, pop, depth, top}
trace(Stack) =
{⟨ ⟩,
⟨push⟩, ⟨depth⟩,
⟨push, pop⟩, ⟨push, top⟩,
⟨push, depth⟩, ⟨push, push⟩,
⟨depth, push⟩, ⟨depth, depth⟩...
Non-EmptyEmpty
depth depth
top
push
pop [depth > 1]
push
pop [depth = 1]
public class Stack_spec
{
public static class A_new_stack
{
@Test
public void has_no_depth() 
@Test()
public void has_no...
public class
Stack_spec
{
public static class
A_new_stack
{
@Test
public void has_no_depth() 
@Test()
public void has_no...
public class
Stack_spec
{
public static class
A_new_stack
{
@Test
public void has_no_depth() 
@Test()
public void has_no...
Propositions
are vehicles
for stating
how things are
or might be.
Thus only indicative
sentences which it
makes sense to think
of as being true or as
being false are
capable of expressing
...
































{P} S {Q}
Are human beings
"noble in reason" and
"infinite in faculty" as
William Shakespeare
famously wrote?
Perfect, "in God's
ima...
Hardly.
def is_leap_year(year):
...
# What is the postcondition?
def is_leap_year(year):
...
# Given
# result = is_leap_year(year)
# Then
# result == (
# year % 4 == 0 and
# year % 100 !=...
def is_leap_year(year):
return (
year % 4 == 0 and
year % 100 != 0 or
year % 400 == 0)
def test_is_leap_year():
...
def test_is_leap_year_works():
...
Express intention
of code usage
with respect to data
class LeapYearSpec:
@test
def years_not_divisible_by_4_are_not_leap_years(self):
assert not is_leap_year(2015)
@test
def y...
class LeapYearSpec:
@test
def years_not_divisible_by_4_are_not_leap_years(self):
assert not is_leap_year(2015)
@test
def y...
def test(function):
function.is_test = True
return function
def check(suite):
tests = [
attr
for attr in (getattr(suite, n...
Express intention
of code usage
with respect to data
Express intention
Naming
Grouping and nesting
of code usage
Realisation of intent
with respect to data
One exemplar or man...
class LeapYearSpec:
@test
def years_not_divisible_by_4_are_not_leap_years(self):
assert not is_leap_year(2015)
assert not ...
class LeapYearSpec:
@test
def years_not_divisible_by_4_are_not_leap_years(self):
assert not is_leap_year(2015)
assert not ...
class LeapYearSpec:
@test
def years_not_divisible_by_4_are_not_leap_years(self):
assert not is_leap_year(2015)
assert not ...
class LeapYearSpec:
@test
def years_not_divisible_by_4_are_not_leap_years(self):
assert not is_leap_year(2015)
assert not ...
class LeapYearSpec:
@test
def years_not_divisible_by_4_are_not_leap_years(self):
assert not is_leap_year(2015)
assert not ...
def test(function):
function.is_test = True
return function
def data(*values):
def prepender(function):
function.data = va...
class LeapYearSpec:
@test
@data(2015)
@data(1999)
@data(1)
def years_not_divisible_by_4_are_not_leap_years(self, year):
as...
class LeapYearSpec:
@test
@data(2015)
@data(1999)
@data(1)
def years_not_divisible_by_4_are_not_leap_years(self, year):
as...
class LeapYearSpec:
@test
@data(2015)
@data(1999)
@data(1)
def years_not_divisible_by_4_are_not_leap_years(self, year):
as...
class LeapYearSpec:
@test
@data(2015)
@data(1999)
@data(1)
def years_not_divisible_by_4_are_not_leap_years(self, year):
as...
class LeapYearSpec:
@test
@data(2015)
@data(1999)
@data(1)
def years_not_divisible_by_4_are_not_leap_years(self, year):
as...
class LeapYearSpec:
@test
@data(2015)
@data(1999)
@data(1)
def years_not_divisible_by_4_are_not_leap_years(self, year):
as...
class LeapYearSpec:
@test
@data(2015)
@data(1999)
@data(1)
def years_not_divisible_by_4_are_not_leap_years(self, year):
as...
Express intention
Naming
Grouping and nesting
of code usage
Realisation of intent
with respect to data
One exemplar or man...
intention
declaration
of intent
Our task is not to find the
maximum amount of
content in a work of art.
Our task is to cut back
content so that we can see...
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Declarative Thinking, Declarative Practice
Upcoming SlideShare
Loading in …5
×

Declarative Thinking, Declarative Practice

1,601 views

Published on

Presented at 8th Light University London (13th May 2016)

Do this, do that. Coding from assembler to shell scripting, from the mainstream languages of the last century to the mainstream languages now, is dominated by an imperative style. From how we teach variables — they vary, right? — to how we talk about databases, we are constantly looking at state as a thing to be changed and programming languages are structured in terms of the mechanics of change — assignment, loops and how code can be threaded (cautiously) with concurrency.

Functional programming, mark-up languages, schemas, persistent data structures and more are all based around a more declarative approach to code, where instead of reasoning in terms of who does what to whom and what the consequences are, relationships and uses are described, and the flow of execution follows from how functions, data and other structures are composed. This talk will look at the differences between imperative and declarative approaches, offering lessons, habits and techniques that are applicable from requirements through to code and tests in mainstream languages.

Published in: Software
  • Be the first to comment

Declarative Thinking, Declarative Practice

  1. 1. declarative thinking, declarative practice @KevlinHenney
  2. 2. Computer Science in the 1960s to 80s spent a lot of effort making languages which were as powerful as possible. Nowadays we have to appreciate the reasons for picking not the most powerful solution but the least powerful. Tim Berners-Lee https://www.w3.org/DesignIssues/Principles.html
  3. 3. The reason for this is that the less powerful the language, the more you can do with the data stored in that language. If you write it in a simple declarative form, anyone can write a program to analyze it in many ways. Tim Berners-Lee https://www.w3.org/DesignIssues/Principles.html
  4. 4. The makefile language is similar to declarative programming. This class of language, in which necessary end conditions are described but the order in which actions are to be taken is not important, is sometimes confusing to programmers used to imperative programming. http://en.wikipedia.org/wiki/Make_(software)
  5. 5. Representation Is the Essence of Programming
  6. 6. Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won’t usually need your flowcharts; they’ll be obvious.
  7. 7. Excel is the world's most popular functional language. Simon Peyton-Jones
  8. 8. try { Integer.parseInt(time.substring(0, 2)); } catch (Exception x) { return false; } if (Integer.parseInt(time.substring(0, 2)) > 12) { return false; } ... if (!time.substring(9, 11).equals("AM") & !time.substring(9, 11).equals("PM")) { return false; } Burk Hufnagel "Put the Mouse Down and Step Away from the Keyboard"
  9. 9. Burk Hufnagel "Put the Mouse Down and Step Away from the Keyboard" public static boolean validateTime(String time) { return time.matches("(0[1-9]|1[0-2]):[0-5][0-9]:[0-5][0-9] ([AP]M)"); }
  10. 10. Many programming languages support programming in both functional and imperative style but the syntax and facilities of a language are typically optimised for only one of these styles, and social factors like coding conventions and libraries often force the programmer towards one of the styles. https://wiki.haskell.org/Functional_programming
  11. 11. intension, n. (Logic)  the set of characteristics or properties by which the referent or referents of a given expression is determined; the sense of an expression that determines its reference in every possible world, as opposed to its actual reference. For example, the intension of prime number may be having non-trivial integral factors, whereas its extension would be the set {2, 3, 5, 7, ...}. E J Borowski and J M Borwein Dictionary of Mathematics
  12. 12. { x2 | x  , x ≥ 1  x ≤ 100 } select from where
  13. 13. A list comprehension is a syntactic construct available in some programming languages for creating a list based on existing lists. It follows the form of the mathematical set-builder notation (set comprehension) as distinct from the use of map and filter functions. http://en.wikipedia.org/wiki/List_comprehension
  14. 14. { x2 | x  , x ≥ 1 }
  15. 15. Lazy evaluation
  16. 16. https://twitter.com/richardadalton/status/591534529086693376
  17. 17. def fizzbuzz(n): result = '' if n % 3 == 0: result += 'Fizz' if n % 5 == 0: result += 'Buzz' if not result: result = str(n) return result
  18. 18. def fizzbuzz(n): if n % 15 == 0: return 'FizzBuzz' elif n % 3 == 0: return 'Fizz' elif n % 5 == 0: return 'Buzz' else: return str(n)
  19. 19. def fizzbuzz(n): return ( 'FizzBuzz' if n % 15 == 0 else 'Fizz' if n % 3 == 0 else 'Buzz' if n % 5 == 0 else str(n))
  20. 20. def fizzbuzz(n): return ( 'FizzBuzz' if n in range(0, 101, 15) else 'Fizz' if n in range(0, 101, 3) else 'Buzz' if n in range(0, 101, 5) else str(n))
  21. 21. fizzes = [''] + ([''] * 2 + ['Fizz']) * 33 + [''] buzzes = [''] + ([''] * 4 + ['Buzz']) * 20 numbers = list(map(str, range(0, 101))) def fizzbuzz(n): return fizzes[n] + buzzes[n] or numbers[n]
  22. 22. 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 ] all(truths)
  23. 23. 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') ] all(truths)
  24. 24. https://twitter.com/wm/status/7206700352
  25. 25. / WordFriday
  26. 26. 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.  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.
  27. 27. // Get the unique surnames in uppercase of the // first 15 book authors that are 50 years old // or older? library.stream() .map(book -> book.getAuthor()) .filter(author -> author.getAge() >= 50) .limit(15) .map(Author::getSurname) .map(String::toUpperCase) .distinct() .collect(toList()))
  28. 28. // Get the first 15 unique surnames in // uppercase of the book authors that are 50 // years old or older. library.stream() .map(book -> book.getAuthor()) .filter(author -> author.getAge() >= 50) .map(Author::getSurname) .map(String::toUpperCase) .distinct() .limit(15) .collect(toList()))
  29. 29. // Get the unique surnames in uppercase of the // first 15 book authors that are 50 years old // or older. library.stream() .map(book -> book.getAuthor()) .filter(author -> author.getAge() >= 50) .distinct() .limit(15) .map(Author::getSurname) .map(String::toUpperCase) .distinct() .collect(toList()))
  30. 30. Simple filters that can be arbitrarily chained are more easily re-used, and more robust, than almost any other kind of code. Brandon Rhodes http://rhodesmill.org/brandon/slides/2012-11-pyconca/
  31. 31. / WordFriday
  32. 32. paraskevidekatriaphobia, noun  The superstitious fear of Friday 13th.  Contrary to popular myth, this superstition is relatively recent (19th century) and did not originate during or before the medieval times.  Paraskevidekatriaphobia also reflects a particularly egocentric attributional bias: the universe is prepared to rearrange causality and probability around the believer based on an arbitrary and changeable calendar system, in a way that is sensitive to geography, culture and time zone.
  33. 33. function NextFriday13thAfter($from) { (1..500) | %{ $from.AddDays($_) } | ?{ $_.Day -eq 13} | ?{ $_.DayOfWeek -eq [DayOfWeek]::Friday } | select –first 1 }
  34. 34. Stack
  35. 35. Stack {push, pop, depth, top}
  36. 36. Stack[T] { push(T), pop(), depth() : Integer, top() : T }
  37. 37. An interface is a contract to deliver a certain amount of service. Clients of the interface depend on the contract, which is usually documented in the interface specification. Butler W Lampson "Hints for Computer System Design"
  38. 38. Stack[T] { push(T item), pop(), depth() : Integer, top() : T } given: before = depth() postcondition: depth() = before + 1 ∧ top() = item precondition: depth() > 0 given: before = depth() precondition: before > 0 postcondition: depth() = before – 1 given: result = depth() postcondition: result ≥ 0
  39. 39. alphabet(Stack) = {push, pop, depth, top}
  40. 40. trace(Stack) = {⟨ ⟩, ⟨push⟩, ⟨depth⟩, ⟨push, pop⟩, ⟨push, top⟩, ⟨push, depth⟩, ⟨push, push⟩, ⟨depth, push⟩, ⟨depth, depth⟩, ⟨push, push, pop⟩, ...}
  41. 41. Non-EmptyEmpty depth depth top push pop [depth > 1] push pop [depth = 1]
  42. 42. public class Stack_spec { public static class A_new_stack { @Test public void has_no_depth()  @Test() public void has_no_top()  } public static class An_empty_stack { @Test() public void throws_when_popped()  @Test public void acquires_depth_by_retaining_a_pushed_item_as_its_top()  } public static class A_non_empty_stack { @Test public void becomes_deeper_by_retaining_a_pushed_item_as_its_top()  @Test public void on_popping_reveals_tops_in_reverse_order_of_pushing()  } }
  43. 43. public class Stack_spec { public static class A_new_stack { @Test public void has_no_depth()  @Test() public void has_no_top()  } public static class An_empty_stack { @Test() public void throws_when_popped()  @Test public void acquires_depth_by_retaining_a_pushed_item_as_its_top()  } public static class A_non_empty_stack { @Test public void becomes_deeper_by_retaining_a_pushed_item_as_its_top()  @Test public void on_popping_reveals_tops_in_reverse_order_of_pushing()  } }
  44. 44. public class Stack_spec { public static class A_new_stack { @Test public void has_no_depth()  @Test() public void has_no_top()  } public static class An_empty_stack { @Test() public void throws_when_popped()  @Test public void acquires_depth_by_retaining_a_pushed_item_as_its_top()  } public static class A_non_empty_stack { @Test public void becomes_deeper_by_retaining_a_pushed_item_as_its_top()  @Test public void on_popping_reveals_tops_in_reverse_order_of_pushing()  } }
  45. 45. Propositions are vehicles for stating how things are or might be.
  46. 46. Thus only indicative sentences which it makes sense to think of as being true or as being false are capable of expressing propositions.
  47. 47.        
  48. 48.      
  49. 49.      
  50. 50.      
  51. 51.   
  52. 52.   
  53. 53. {P} S {Q}
  54. 54. Are human beings "noble in reason" and "infinite in faculty" as William Shakespeare famously wrote? Perfect, "in God's image," as some biblical scholars have asserted?
  55. 55. Hardly.
  56. 56. def is_leap_year(year): ... # What is the postcondition?
  57. 57. def is_leap_year(year): ... # Given # result = is_leap_year(year) # Then # result == ( # year % 4 == 0 and # year % 100 != 0 or # year % 400 == 0)
  58. 58. def is_leap_year(year): return ( year % 4 == 0 and year % 100 != 0 or year % 400 == 0)
  59. 59. def test_is_leap_year(): ...
  60. 60. def test_is_leap_year_works(): ...
  61. 61. Express intention of code usage with respect to data
  62. 62. class LeapYearSpec: @test def years_not_divisible_by_4_are_not_leap_years(self): assert not is_leap_year(2015) @test def years_divisible_by_4_but_not_by_100_are_leap_years(self): assert is_leap_year(2016) @test def years_divisible_by_100_but_not_by_400_are_not_leap_years(self): assert not is_leap_year(1900) @test def years_divisible_by_400_are_leap_years(self): assert is_leap_year(2000)
  63. 63. class LeapYearSpec: @test def years_not_divisible_by_4_are_not_leap_years(self): assert not is_leap_year(2015) @test def years_divisible_by_4_but_not_by_100_are_leap_years(self): assert is_leap_year(2016) @test def years_divisible_by_100_but_not_by_400_are_not_leap_years(self): assert not is_leap_year(1900) @test def years_divisible_by_400_are_leap_years(self): assert is_leap_year(2000)
  64. 64. def test(function): function.is_test = True return function def check(suite): tests = [ attr for attr in (getattr(suite, name) for name in dir(suite)) if callable(attr) and hasattr(attr, 'is_test')] for to_test in tests: try: to_test(suite()) except: print('Failed: ' + to_test.__name__ + '()')
  65. 65. Express intention of code usage with respect to data
  66. 66. Express intention Naming Grouping and nesting of code usage Realisation of intent with respect to data One exemplar or many Explicit or generated
  67. 67. class LeapYearSpec: @test def years_not_divisible_by_4_are_not_leap_years(self): assert not is_leap_year(2015) assert not is_leap_year(1999) assert not is_leap_year(1) @test def years_divisible_by_4_but_not_by_100_are_leap_years(self): assert is_leap_year(2016) @test def years_divisible_by_100_but_not_by_400_are_not_leap_years(self): assert not is_leap_year(1900) @test def years_divisible_by_400_are_leap_years(self): assert is_leap_year(2000)
  68. 68. class LeapYearSpec: @test def years_not_divisible_by_4_are_not_leap_years(self): assert not is_leap_year(2015) assert not is_leap_year(1999) assert not is_leap_year(1) @test def years_divisible_by_4_but_not_by_100_are_leap_years(self): assert is_leap_year(2016) assert is_leap_year(1984) assert is_leap_year(4) @test def years_divisible_by_100_but_not_by_400_are_not_leap_years(self): assert not is_leap_year(1900) @test def years_divisible_by_400_are_leap_years(self): assert is_leap_year(2000)
  69. 69. class LeapYearSpec: @test def years_not_divisible_by_4_are_not_leap_years(self): assert not is_leap_year(2015) assert not is_leap_year(1999) assert not is_leap_year(1) @test def years_divisible_by_4_but_not_by_100_are_leap_years(self): assert is_leap_year(2016) assert is_leap_year(1984) assert is_leap_year(4) @test def years_divisible_by_100_but_not_by_400_are_not_leap_years(self): assert not is_leap_year(1900) @test def years_divisible_by_400_are_leap_years(self): for year in range(400, 2401, 400): assert is_leap_year(year)
  70. 70. class LeapYearSpec: @test def years_not_divisible_by_4_are_not_leap_years(self): assert not is_leap_year(2015) assert not is_leap_year(1999) assert not is_leap_year(1) @test def years_divisible_by_4_but_not_by_100_are_leap_years(self): assert is_leap_year(2016) assert is_leap_year(1984) assert is_leap_year(4) @test def years_divisible_by_100_but_not_by_400_are_not_leap_years(self): for year in range(100, 2101, 100): if year % 400 != 0: assert not is_leap_year(year) @test def years_divisible_by_400_are_leap_years(self): for year in range(400, 2401, 400): assert is_leap_year(year)
  71. 71. class LeapYearSpec: @test def years_not_divisible_by_4_are_not_leap_years(self): assert not is_leap_year(2015) assert not is_leap_year(1999) assert not is_leap_year(1) @test def years_divisible_by_4_but_not_by_100_are_leap_years(self): assert is_leap_year(2016) assert is_leap_year(1984) assert is_leap_year(4) @test def years_divisible_by_100_but_not_by_400_are_not_leap_years(self): for year in range(100, 2101, 100): if year % 400 != 0: assert not is_leap_year(year) @test def years_divisible_by_400_are_leap_years(self): for year in range(400, 2401, 400): assert is_leap_year(year)
  72. 72. def test(function): function.is_test = True return function def data(*values): def prepender(function): function.data = values + getattr(function, 'data', ()) return function return prepender def check(suite): tests = [ attr for attr in (getattr(suite, name) for name in dir(suite)) if callable(attr) and hasattr(attr, 'is_test')] for to_test in tests: try: if hasattr(to_test, 'data'): for value in to_test.data: call = '(' + str(value) + ')' to_test(suite(), value) else: call = '()' to_test(suite()) except: print('Failed: ' + to_test.__name__ + call)
  73. 73. class LeapYearSpec: @test @data(2015) @data(1999) @data(1) def years_not_divisible_by_4_are_not_leap_years(self, year): assert not is_leap_year(year) @test def years_divisible_by_4_but_not_by_100_are_leap_years(self): assert is_leap_year(2016) @test def years_divisible_by_100_but_not_by_400_are_not_leap_years(self): assert not is_leap_year(1900) @test def years_divisible_by_400_are_leap_years(self): assert is_leap_year(2000)
  74. 74. class LeapYearSpec: @test @data(2015) @data(1999) @data(1) def years_not_divisible_by_4_are_not_leap_years(self, year): assert not is_leap_year(year) @test @data(2016, 1984, 4) def years_divisible_by_4_but_not_by_100_are_leap_years(self, year): assert is_leap_year(year) @test def years_divisible_by_100_but_not_by_400_are_not_leap_years(self): assert not is_leap_year(1900) @test def years_divisible_by_400_are_leap_years(self): assert is_leap_year(2000)
  75. 75. class LeapYearSpec: @test @data(2015) @data(1999) @data(1) def years_not_divisible_by_4_are_not_leap_years(self, year): assert not is_leap_year(year) @test @data(2016, 1984, 4) def years_divisible_by_4_but_not_by_100_are_leap_years(self, year): assert is_leap_year(year) @test def years_divisible_by_100_but_not_by_400_are_not_leap_years(self): assert not is_leap_year(1900) @test @data(*range(400, 2401, 400)) def years_divisible_by_400_are_leap_years(self, year): assert is_leap_year(year)
  76. 76. class LeapYearSpec: @test @data(2015) @data(1999) @data(1) def years_not_divisible_by_4_are_not_leap_years(self, year): assert not is_leap_year(year) @test @data(2016, 1984, 4) def years_divisible_by_4_but_not_by_100_are_leap_years(self, year): assert is_leap_year(year) @test @data(*(year for year in range(100, 2101, 100) if year % 400 != 0)) def years_divisible_by_100_but_not_by_400_are_not_leap_years(self, year): assert not is_leap_year(year) @test @data(*range(400, 2401, 400)) def years_divisible_by_400_are_leap_years(self, year): assert is_leap_year(year)
  77. 77. class LeapYearSpec: @test @data(2015) @data(1999) @data(1) def years_not_divisible_by_4_are_not_leap_years(self, year): assert not is_leap_year(year) @test @data(2016, 1984, 4) def years_divisible_by_4_but_not_by_100_are_leap_years(self, year): assert is_leap_year(year) @test @data(*range(100, 2101, 400)) @data(*range(200, 2101, 400)) @data(*range(300, 2101, 400)) def years_divisible_by_100_but_not_by_400_are_not_leap_years(self, year): assert not is_leap_year(year) @test @data(*range(400, 2401, 400)) def years_divisible_by_400_are_leap_years(self, year): assert is_leap_year(year)
  78. 78. class LeapYearSpec: @test @data(2015) @data(1999) @data(1) def years_not_divisible_by_4_are_not_leap_years(self, year): assert not is_leap_year(year) @test @data(2016, 1984, 4) def years_divisible_by_4_but_not_by_100_are_leap_years(self, year): assert is_leap_year(year) @test @data(*range(100, 2101, 400)) @data(*range(200, 2101, 400)) @data(*range(300, 2101, 400)) def years_divisible_by_100_but_not_by_400_are_not_leap_years(self, year): assert not is_leap_year(year) @test @data(*range(400, 2401, 400)) def years_divisible_by_400_are_leap_years(self, year): assert is_leap_year(year)
  79. 79. class LeapYearSpec: @test @data(2015) @data(1999) @data(1) def years_not_divisible_by_4_are_not_leap_years(self, year): assert not is_leap_year(year) @test @data(2016, 1984, 4) def years_divisible_by_4_but_not_by_100_are_leap_years(self, year): assert is_leap_year(year) @test @data(*range(100, 2101, 400)) @data(*range(200, 2101, 400)) @data(*range(300, 2101, 400)) def years_divisible_by_100_but_not_by_400_are_not_leap_years(self, year): assert not is_leap_year(year) @test @data(*range(400, 2401, 400)) def years_divisible_by_400_are_leap_years(self, year): assert is_leap_year(year)
  80. 80. Express intention Naming Grouping and nesting of code usage Realisation of intent with respect to data One exemplar or many Explicit or generated
  81. 81. intention
  82. 82. declaration of intent
  83. 83. Our task is not to find the maximum amount of content in a work of art. Our task is to cut back content so that we can see the thing at all. Susan Sontag

×