Day 10 - Tests for tables & privacy analysis
Skills: 1, 2, 11
Pre-reading: 4.1.5, 4.1.6
Supplementary Videos
Tests for Table-Producing Functions, Lambda Functions
Reference: For all work with tables, refer to the Tables page in the menu at the top of the page!
Announcement -- tests for your tests in autograder (10 mins)
-
Start with HW3, our autograder runs against your tests, not just your code.
-
"Your tests against our correct implementation(s)" means we take your tests and make sure they pass on an implementation that works. For example, consider the
square
function that is an example for Skill 1. If you wrote:fun square(n :: Number) -> Number:
doc: "squares"
n * n
where:
square(1) is 1
endThen we might run your tests (just one!) against a correct implementation:
fun square(n :: Number) -> Number:
doc: "a correct square"
num-sqr(n)
endAnd in this case, all your tests pass, so you would get credit. If you had mis-interpreted the question as
square root
and had done:fun square(n :: Number) -> Number:
doc: "squares"
num-sqrt(n)
where:
square(1) is 1
square(4) is 2
endThen you would not pass this, since when we combine our implementation with your tests, we get:
fun square(n :: Number) -> Number:
doc: "a correct square"
num-sqr(n)
where:
square(1) is 1
square(4) is 2
endAnd the second test fails.
-
"Your tests against our incorrect implementation(s)" means we take your tests and run them on implementations that have bugs we know! We put them in! And the goal is that at least one of your tests fails. This means your tests are thorough enough to cover the buggy behavior in our implementation. If you had:
fun square(n :: Number) -> Number:
doc: "squares"
n * n
where:
square(1) is 1
endWe might have an incorrect (i.e., buggy) implementation as:
fun square(n :: Number) -> Number
doc: "an incorrect square -- just returns input"
n
endBut even though this definitely isn't square, when we combine your tests with our implementation:
fun square(n :: Number) -> Number
doc: "an incorrect square -- just returns input"
n
where:
square(1) is 1
endAll your tests (just one, here) pass! Which is not good. The reason they pass is that your tests were not good enough to distinguish between a function that just returned the input, doing nothing to it, and actually squaring. This test you have isn't wrong (so you shouldn't remove it!) -- it doesn't fail on the correct implementation -- but you should add more tests, to ensure you cover more behavior. Adding just one more test, to make your submission look like:
fun square(n :: Number) -> Number:
doc: "squares"
n * n
where:
square(1) is 1
square(2) is 4
endIs enough for you to catch our bug, since when we combine all your test with out buggy implementation, one of your tests now fails!
fun square(n :: Number) -> Number
doc: "an incorrect square -- just returns input"
n
where:
square(1) is 1
square(2) is 4
end- As you see in the screenshot above, we may (and often do) have multiple incorrect implementations, that have different bugs. So if you catch some, but not all, think about what cases your tests aren't covering (for example, above I don't have any inputs that are negative. And I don't have zero!).
Intro (15 mins)
-
In the last few classes, we've shown table operations that take functions as arguments; some of those are complex, and benefit from being written with our full design process (e.g.,
calc-distance
, or the function to obfuscate item names), but others, likesubtract-1
seem a bit unnecessary. It would be nice if we could write:t = table: x-coord :: Number, y-coord :: Number
row: 1, 2
row: 3, 4
end
transform-column(t, "x-coord", n - 1) -
But if we try to run this, we get an error --
n
is not defined. What we want, instead ofn
, is a way of referring to the column value that is being transformed. When we defined a function, this is the argument:fun subtract-1(n :: Number) -> Number:
doc: "subtracts 1 from input"
n - 1
where:
subtract-1(10) is 9
subtract-1(0) is -1
subtract-1(-3.5) is -4.5
end -
For simple functions, we can do this with
lam
, which creates an unnamed function:transform-column(t, "x-coord", lam(n :: Number) -> Number: n - 1 end)
-
Another oddity about the last few classes is that while we've followed our design process for helper functions, we haven't actually written functions that take tables and produce tables, and (for the most part), haven't written tests on tables.
-
For example, let's design a function that transforms a table, modifying a
"price"
column by discounting it by 20%, but only for prices that are below 100.fun apply-discounts(t :: Table) -> Table:
doc: "transforms 'price' column by reducing 20%, if value is below 100"
transform-column(t, "price", lam(price :: Number) -> Number:
if price < 100: price * 0.8 else: price end
end)
where:
test-table =
table: price
row: 50
row: 120
row: 80
row: 40
end
apply-discounts(test-table) is
table: price
row: 50 * 0.8
row: 120
row: 80 * 0.8
row: 40 * 0.8
end
end -
Note how we created an example table within our
where:
block, and in the test, rather than computing the actual numbers, wrote the expressions for the rows that should have changed. This makes it easier to understand the behavior. -
It's often helpful to see the data, using visualizations. There are many functions that allow us to make charts. For example, we can make a
freq-bar-chart
using:prices = table: price
row: 50
row: 120
row: 80
row: 40
row: 50
row: 80
row: 80
end
freq-bar-chart(prices, "price") -
Finally, while you read about the privacy analysis in the pre-reading for Day 6, and have seen it in labs, we never did an exercise, we'll do that in class today.
Class Exercise (30 mins)
- Design a function that takes a table that has a "price" column and adds a new column "tax", which is the sales tax amount (look up the sales tax where you are; to calculate the sales tax rate, multiply that by the price. If you live somewhere with no sales tax, you can use 5%). You can assume there is not already a tax column.
- Create a function to carry out the "obfuscation" exercise from last class, and
write tests for it. Note, your test tables should only need an
"item"
column! - Consider our privacy analysis, which is based on answering five questions:
Question Answer What type of information is shared? Who is the subject of the information? Who is the sender of the information? Who are the potential recipients of the information? What principles govern the collection and transmission of this information? - Apply that analysis to the Shamazon resume tool that we covered in Day 6.
Wrap-up (5 mins)
lam
allows us to more concisely use operations that expect functions as inputs, liketransform-column
,build-column
, orfilter-with
.- Table functions should follow the same design process, and include tests, just like other functions!