<div dir="ltr"><div class="gmail_quote"><div dir="ltr">On Fri, 28 Sep 2018 at 02:24, Marko Ristin-Kaufmann <<a href="mailto:marko.ristin@gmail.com" target="_blank">marko.ristin@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div>Hi,<br><br></div>I annotated pathlib with contracts:<a href="https://github.com/mristin/icontract-pathlib-poc" target="_blank">https://github.com/mristin/icontract-pathlib-poc</a>. I zipped the HTML docs into <a href="https://github.com/mristin/icontract-pathlib-poc/blob/master/contracts-pathlib-poc.zip" target="_blank">https://github.com/mristin/icontract-pathlib-poc/blob/master/contracts-pathlib-poc.zip</a>, you can just unpack and view the index.html.<br></div></div></div></div></div></div></blockquote><div><br></div><div>Thanks, for doing this! It's probably not going to result in the reaction you hoped for (see below) but I appreciate you taking the time to do it.</div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"></div><div>Some of the contracts might seem trivial -- but mind that you, as a writer, want to tell the user what s/he is expected to fulfill before calling the function. For example, if you say:<br></div><div><dl class="m_-2962710324313643408m_2031365991680776821gmail-method"><dt id="m_-2962710324313643408m_2031365991680776821gmail-mypathlib.Path.rmdir">
<code class="m_-2962710324313643408m_2031365991680776821gmail-descname">rmdir</code><span class="m_-2962710324313643408m_2031365991680776821gmail-sig-paren">(</span><span class="m_-2962710324313643408m_2031365991680776821gmail-sig-paren">)</span></dt><dd><p>Remove this directory.  The directory must be empty.</p>
<table class="m_-2962710324313643408m_2031365991680776821gmail-docutils m_-2962710324313643408m_2031365991680776821gmail-field-list" frame="void" rules="none">
<colgroup><col class="m_-2962710324313643408m_2031365991680776821gmail-field-name">
<col class="m_-2962710324313643408m_2031365991680776821gmail-field-body">
</colgroup><tbody valign="top">
<tr class="m_-2962710324313643408m_2031365991680776821gmail-field-odd m_-2962710324313643408m_2031365991680776821gmail-field"><th class="m_-2962710324313643408m_2031365991680776821gmail-field-name">Requires:</th><td class="m_-2962710324313643408m_2031365991680776821gmail-field-body"><ul class="m_-2962710324313643408m_2031365991680776821gmail-first m_-2962710324313643408m_2031365991680776821gmail-last m_-2962710324313643408m_2031365991680776821gmail-simple"><li><code class="m_-2962710324313643408m_2031365991680776821gmail-code m_-2962710324313643408m_2031365991680776821gmail-docutils m_-2962710324313643408m_2031365991680776821gmail-literal m_-2962710324313643408m_2031365991680776821gmail-notranslate"><span class="m_-2962710324313643408m_2031365991680776821gmail-pre">not</span> <span class="m_-2962710324313643408m_2031365991680776821gmail-pre">list(self.iterdir())</span></code> (??? There must be a way to check this more optimally)</li><li><code class="m_-2962710324313643408m_2031365991680776821gmail-code m_-2962710324313643408m_2031365991680776821gmail-docutils m_-2962710324313643408m_2031365991680776821gmail-literal m_-2962710324313643408m_2031365991680776821gmail-notranslate"><span class="m_-2962710324313643408m_2031365991680776821gmail-pre">self.is_dir()</span></code></li></ul></td></tr></tbody></table></dd></dl><br></div><div>self.is_dir() contract might seem trivial -- but it's actually not. You actually want to convey the message: dear user, if you are iterating through a list of paths, use this function to decide if you should call rmdir() or unlink(). Analogously with the first contract: dear user, please check if the directory is empty before calling rmdir() and this is what you need to call to actually check that. <br></div></div></div></div></div></div></blockquote><div><br></div><div>The problem (IMO) with both of these is precisely that they are written as Python expressions. Your prose descriptions of what they mean are fine, and *those* are what I would hope to see in documentation. This is far more obvious in later examples, where the code needed to check certain conditions is extremely unclear unless you spend time trying to interpret it.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div></div><div>I also finally assembled the puzzle. Most of you folks are all older and were exposed to DbC in the 80ies championed by DbC zealots who advertised it as <i>the </i>tool for software development. You were repulsed by their fanaticism -- the zealots also pushed for all the contracts to be defined, and none less. Either you have 100% DbC or no sane software development at all.<br></div></div></div></div></div></div></blockquote><div><br></div><div>Well, yes, but your claims were *precisely* the same as those I saw back then. "All projects on PyPI would benefit", "the benefits are obvious", ...</div><div><br></div><div>As I said, DbC is a reasonable methodology, but the reason it's not more widely used is (in the first instance) because it has a *marketing* problem. Solving that with more of the same style of marketing won't help. Once you get beyond the marketing, there are *still* questions (see above and below), but if you can't even get people past the first step, you've lost.</div><div> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div></div><div>And that's why I said that the libraries on pypi meant to be used by multiple people and which already have type annotations would obviously benefit from contracts -- while you were imagining that all of these libraries need to be DbC'ed 100%, I was imagining something much more humble. Thus the misunderstanding.<br></div></div></div></div></div></div></blockquote><div><br></div><div>No, I was imagining that some libraries were small, some were used by small, specialised groups, and some were immensely successful without DbC. So claiming that they would "obviously" benefit is a very strong claim.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div>After annotating pathlib, I find that it actually needs contracts more thain if it had type annotations. For example:<br><dl class="m_-2962710324313643408m_2031365991680776821gmail-method"><dt id="m_-2962710324313643408m_2031365991680776821gmail-mypathlib.Path.stat">
<code class="m_-2962710324313643408m_2031365991680776821gmail-descname">stat</code><span class="m_-2962710324313643408m_2031365991680776821gmail-sig-paren">(</span><span class="m_-2962710324313643408m_2031365991680776821gmail-sig-paren">)</span></dt><dd><p>Return the result of the stat() system call on this path, like
os.stat() does.</p>
<table class="m_-2962710324313643408m_2031365991680776821gmail-docutils m_-2962710324313643408m_2031365991680776821gmail-field-list" frame="void" rules="none">
<colgroup><col class="m_-2962710324313643408m_2031365991680776821gmail-field-name">
<col class="m_-2962710324313643408m_2031365991680776821gmail-field-body">
</colgroup><tbody valign="top">
<tr class="m_-2962710324313643408m_2031365991680776821gmail-field-odd m_-2962710324313643408m_2031365991680776821gmail-field"><th class="m_-2962710324313643408m_2031365991680776821gmail-field-name">Ensures:</th><td class="m_-2962710324313643408m_2031365991680776821gmail-field-body"><ul class="m_-2962710324313643408m_2031365991680776821gmail-first m_-2962710324313643408m_2031365991680776821gmail-last m_-2962710324313643408m_2031365991680776821gmail-simple"><li><code class="m_-2962710324313643408m_2031365991680776821gmail-code m_-2962710324313643408m_2031365991680776821gmail-docutils m_-2962710324313643408m_2031365991680776821gmail-literal m_-2962710324313643408m_2031365991680776821gmail-notranslate"><span class="m_-2962710324313643408m_2031365991680776821gmail-pre">result</span> <span class="m_-2962710324313643408m_2031365991680776821gmail-pre">is</span> <span class="m_-2962710324313643408m_2031365991680776821gmail-pre">not</span> <span class="m_-2962710324313643408m_2031365991680776821gmail-pre">None</span></code> ⇒ <code class="m_-2962710324313643408m_2031365991680776821gmail-code m_-2962710324313643408m_2031365991680776821gmail-docutils m_-2962710324313643408m_2031365991680776821gmail-literal m_-2962710324313643408m_2031365991680776821gmail-notranslate"><span class="m_-2962710324313643408m_2031365991680776821gmail-pre">self.exists()</span></code></li><li><code class="m_-2962710324313643408m_2031365991680776821gmail-code m_-2962710324313643408m_2031365991680776821gmail-docutils m_-2962710324313643408m_2031365991680776821gmail-literal m_-2962710324313643408m_2031365991680776821gmail-notranslate"><span class="m_-2962710324313643408m_2031365991680776821gmail-pre">result</span> <span class="m_-2962710324313643408m_2031365991680776821gmail-pre">is</span> <span class="m_-2962710324313643408m_2031365991680776821gmail-pre">not</span> <span class="m_-2962710324313643408m_2031365991680776821gmail-pre">None</span></code> ⇒ <code class="m_-2962710324313643408m_2031365991680776821gmail-code m_-2962710324313643408m_2031365991680776821gmail-docutils m_-2962710324313643408m_2031365991680776821gmail-literal m_-2962710324313643408m_2031365991680776821gmail-notranslate"><span class="m_-2962710324313643408m_2031365991680776821gmail-pre">os.stat(str(self)).__dict__</span> <span class="m_-2962710324313643408m_2031365991680776821gmail-pre">==</span> <span class="m_-2962710324313643408m_2031365991680776821gmail-pre">result.__dict__</span></code> (??? This is probably not what it was meant with ‘like os.stat() does’?)</li></ul></td></tr></tbody></table></dd></dl><br></div><div>But what does it mean "like os.stat() does"? I wrote equality over __dict__'s in the contract. That was my idea of what the implementer was trying to tell me. But is that the operator that should be applied? Sure, the contract merits a description. But without it, how am I supposed to know what "like" means?<br><br></div><div>Similarly with rmdir() -- "the directory must be empty" -- but how exactly am I supposed to check that?<br></div></div></div></div></div></div></blockquote><div><br></div><div>Isn't that the whole point? The prose statement "the directory must be empty" is clear. But the exact code check isn't - and may be best handled by a series of unit tests, rather than a precondition.</div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div>Anyhow, please have a look at the contracts and let me know what you think. Please consider it an illustration. Try to question whether the contracts I wrote are so obvious to everybody even if they are obvious to you and keep in mind that the user does not look into the implementation. And please try to abstract away the aesthetics: neither icontract library that I wrote nor the sphinx extension are of sufficient quality. We use them for our company code base, but they still need a lot of polishing. So please try to focus only on the content. We are still talking about contracts in general, not about the concrete contract implementation</div></div></div></div></div></div></blockquote><div><br></div><div>The thing that you didn't discuss in the above was the effect on the source code. Looking at your modified sources, I found it *significantly* harder to read your annotated version than the original. Basically every function and class was cluttered with irrelevant[1] conditions, which obscured the logic and the basic meaning of the code.</div><div><br></div><div>[1] Irrelevant in terms of the flow of the code - I appreciate that there's value in checking preconditions in the broader sense. It's like all error handling and similar - there's a balance to be had between "normal behaviour" and handling of exceptional cases. And I feel that the contracts tip that balance too far towards making exceptional cases the focus.</div><div><br></div><div>So ultimately this example has probably persuaded me that I *don't* want to add contract checking, except in very specific cases where the benefits outweigh the disadvantages. It's very subjective, though, so I'm fine if other people feel differently.</div><div><br></div><div>Paul</div></div></div>