Choosing the inputs¶
A claim has two halves: the rule, and the inputs it covers. The rule is
usually the easy part to write. This page is about the other half, the
for ... in that says which inputs the function has to get right,
because that choice decides what a verdict means.
The text languages¶
A string's domain is a language, written L[...]. These cover most
code:
| Language | Every string of |
|---|---|
L[unicode] |
any code points at all, lone surrogates and controls included |
L[printable] |
characters that print (str.isprintable) |
L[latin-1] |
code points up to 0xff |
L[ascii] |
code points up to 0x7f |
L[alpha], L[alnum], L[digit] |
ASCII letters, letters and digits, digits |
L[unicode_alpha], L[unicode_alnum] |
letters, or letters and digits, in any script |
L[identifier] |
a Python identifier |
Languages lists the rest, formats such as L[uuid] and
L[email] among them, and Formats uses them.
Narrowing by length¶
The shop's header shows the username upper-cased, in a box 32 characters
wide, and usernames are at most 32 characters. The function is in
shop/text.py, as is every function on this page:
def display_name(username: str) -> str:
"""The username as shown in the header, upper-cased."""
return username.upper()
len <= 32 inside the brackets narrows a language to its strings of at
most 32 code points, so the claim that the header fits is the one
below. As before, the first line is the claim, which you can check with
mathema check shop/text.py:display_name --claim "..." or keep in
display_name's docstring, and the indented line is the result:
for username in L[unicode, len <= 32], len(display_name(username)) <= 32
falsified username = 'afifififififififififififififififi': 33 vs 32
username 'afififi…fi' 17 code points: 'a', then 16 of U+FB01 LATIN SMALL LIGATURE FI
header 'AFIFIFI…FI' 33 code points, since each fi upper-cases to two
Upper-casing can make a string longer. It is tempting to decide that
usernames are European and move on, but L[latin-1] fails the same way,
on the German sharp s:
for username in L[latin-1, len <= 32], len(display_name(username)) <= 32
falsified username = 'aßßßßßßßßßßßßßßßß': 33 vs 32
Only ASCII keeps its length when upper-cased:
for username in L[ascii, len <= 32], len(display_name(username)) <= 32
holds
So there are two honest fixes, and the claim makes you pick one. Either
usernames really are ASCII, in which case the signup form should refuse
anything else and the claim over L[ascii, len <= 32] is the right one,
or they aren't, and display_name has to cut its result to 32 after
upper-casing, not before. What won't do is a claim over L[ascii] for a
form that takes any text: the claim would hold, and say nothing about
the names the shop actually gets.
Leaving values out¶
The avatar shows the first letter of a user's name:
def first_initial(name: str) -> str:
"""The avatar letter shown for a user."""
return name[0].upper()
for name in L[unicode], len(first_initial(name)) == 1
falsified name = '': raised IndexError…
A name with nothing in it has no first letter. If the shop never stores
an empty name, say so by leaving it out with \, the set difference:
for name in L[unicode] \ {''}, len(first_initial(name)) == 1
falsified name = 'ΐ': 3 vs 1
L[unicode, len >= 1] says the same thing as L[unicode] \ {''}; the
exclusion is for any particular value, such as the 'NA' a spreadsheet
writes for a missing name.
The second failure is different. ΐ (Greek iota with dialytika and
tonos) upper-cases to three code points, Ϊ́, which a screen still shows
as one letter. The code is fine; it is the claim that asked for the
wrong thing, one code point, when what the avatar needs is at least one:
for name in L[unicode] \ {''}, len(first_initial(name)) >= 1
holds
Most falsified claims end one of these two ways, a change to the code or a change to the claim, and both are progress: either way the rule is now written down and checked.
Choosing well¶
Start from what the function is actually given, not from what makes the
claim pass. If the input comes from a form, a file or another service,
that is usually L[unicode], and every narrowing (a shorter length, a
smaller alphabet, a value left out) should be one the code that calls
the function really enforces.
Next: Formats.