On Mon, Oct 08, 2018 at 03:34:51PM -0400, Terry Reedy wrote:
I said above that functions may be specified by process rather than result.
Fine. What of it? Can you describe what the function does?
"It sorts the list in place."
"It deletes the given record from the database."
These are all post-conditions.
No they are not.
Yes they are; they are textual descriptions of the post-condition, not executable code, but since software contracts are both specification and code, they certainly count as post-conditions.
By the way, you seem to have deleted one of my examples, leaving only the two relatively simple ones. I don't understand why you deleted the example, but here it is again:
"It deducts the given amount from Account A and transfers it to Account B, guaranteeing that either both transactions occur or neither of them, but never one and not the other."
They are descriptions of the process. Additional mental work is required to turn them into formal descriptions of the result that can be coded.
Yes. That's called "programming". You might have done it from time to time :-)
Marko appears to claim that such coded formal descriptions are easier to read and understand than the short English description. I disagree.
What does "sort in place"? *Precisely*? How do you know if the software *actually does* sort in place?
They are not rhetorical questions.
You can document that the function sorts in place, but if it actually reverses a copy of the list, how would you know?
How do we know the software meets the specified requirements if you haven't nailed down the specification, and if the specification exists only in documentation and is never checked in the code?
Design By Contract is a methodology that helps ensure that the specification is (1) nailed down and (2) actually checked.
It is therefore not obvious to me that the extra work is worthwhile.
Tell me how you, personally, would find out whether or not the function actually sorts the list in place. What process would YOU do?
As I see it, you either have to take it on trust, or you have to write tests. Would you argue that the "extra work" to write tests are not worthwhile?
def append_first(seq): "Append seq to seq."
The snipped body (revised to omit irrelevant 'return') seq.append(seq)
But with duck-typing, no post condition is possible.
def append_first(seq): require: len(seq) > 0
seq does not neccessarily have a __len__ method
Yes it must, because I wrote the specification and I demand that it must support len.
You lost your opportunity to write the spec yourself when you wrongly said no post-condition is possible. I just showed that post-conditions ARE possible, by writing some.
We could debate what those conditions are: can we just test for the Sequence interface, using isinstance() and the Sequence ABC? Do I need to bother checking that seq supports __getitem__?
But I'm not really interested in getting bogged down in the minutia of what specific post-conditions need to go with this toy function. Its a made-up example, so who cares? In practice, you specify in as much detail as you need. You can always come back and add more detail later.
The actual precondition is that seq be in the domain of seq.append.
That is not part of the contract of the append_first function. It doesn't care about any domain limits of seq. If an object could be appended into seq in the first place, surely it can append again later. We don't have to try to detect every imaginable bizarre weird piece of code intentionally written to perplex us.
The only absolutely sure way to test this is to run the code body.
Sure. So what? The code body does actually get run you know. If it raises an exception because seq can't append seq to itself, that's a bug that needs to be fixed.
Or one could run seq and check it against the preconditions, if formally specified, of seq.append.
That is not the responsibility of the append_first function.
ensure: len(seq) == len(OLD.seq) + 1 seq == seq[-1]
Not even all sequences implement negative indexing.
I don't care. Since I wrote the spec, I demand that only those which meet my requirements are used.
But okay, fine, I'll re-write it:
seq == seq[len(seq)-1]
and then you can tell me off for writing un-Pythonic code, and then I'll change it back.
This is true for lists, as I said, but not for every object the meets the preconditions. As others have said, duck typing means that we don't know what unexpected things methods of user-defined classes might do.
That's not how we use duck-typing. When I call len(obj), I don't expect that it will format my hard drive, even though obj.__len__ might do that, and if it did, I would treat it as a bug. Why am I passing such an object as input? That makes no sense. I *don't want* that behaviour, so why would I pass an object that does that?
I wouldn't say "Oh well, that means that formatting my head drive must be the correct behaviour for my application, good thing I have backups."
I would say "That's a bug. Fix it."
Here is the scenario: I am writing an application that has some specific purpose. I don't know, let's say it is a payroll application. It talks to a database and generates payroll reports or something. Whatever. The point is it has some *concrete* purpose, it's not just Mickey Mouse toy functions.
As part of that application, I need (for some unspecified reason!) to take a sequence, extract the first item, and append it to the end of the sequence. Hence you append_first function. Why? Who knows. Let's say it makes sense in context. I trust you.
In context, the sequence I pass to that function is not going to be "some arbitrary object that could do absolutely anything". It is going to be from a tiny subset of *useful* sequences, specifically the ones which actually do append the first item to the end of sequence. Why would I write, and then use, a class that does something unhelpful? Am I a malicious coder trying to get myself fired?
The purpose of the contract is to detect ACCIDENTAL bugs early, not to defend against intentional self-sabotage.