Why is design-by-contracts not widely adopted?

[munch]
Their users would hugely benefit from a more mature and standardized contracts library with informative violation messages.
Will respond in another message, because it's a big topic.
I argue that Design by Contract doesn't make sense for Python and other dynamically typed, duck typed languages because it's contrary to how the language, and the programmer, expects to work. In Python we can write something like: def foo(x): x.bar(y) What's the type of x? What's the type of y? What is the contract of bar? Don't know, don't care. x, or y, can be an instance, a class, a module, a proxy for a remote web service. The only "contract" is that object x will respond to message bar that takes one argument. Object x, do whatever you want with it. And that's a feature, not a bug, not bad design. It follows Postel's Law for Internet protocols of being liberal in what you accept. It follows the Agile principle of valuing working software over comprehensive doco. It allows software components to be glued together quickly and easily. It's a style of programming that has been successful for many years, not just in Python but also in Lisp and Smalltalk and Perl and JavaScript. It works. Not for everything. If I were writing the avionics control routines for a helicopter gas turbine, I'd use formal notation and static type checking and preconditions and whatnot. But I wouldn't be using Python either.
You left off option 6), plain text. Comments. Docstrings. README files. Web pages. Books. In my experience, this is what most people consider documentation. A good book, a good blog post, can explain more about how a library works and what the implementation requirements and restrictions are than formal contract notation. In particular, contracts in Eiffel don't explain *why* they're there. As for 4) reading the code, why not? "Use the source, Luke" is now a programming cliche because it works. It's particularly appropriate for Python packages which are usually distributed in source form and, as you yourself noted, easy to read. -- cheers, Hugh Fisher

Hi Hugh,
That was actually the option 2):
2) Write precondtions and postconditions in docstring of the method as human text.
The problem with text is that it is not verifiable and hence starts to "rot". Noticing that text is wrong involves much more developer time & attention than automatically verifying the formal contracts. In Python we can write something like: def foo(x): x.bar(y) What's the type of x? What's the type of y? What is the contract of bar? Don't know, don't care. x, or y, can be an instance, a class, a module, a proxy for a remote web service. The only "contract" is that object x will respond to message bar that takes one argument. Object x, do whatever you want with it. I still don't see how this is connected to contracts or how contracts play a role there? If foo can accept any x and return any result then there is *no *contract. But hardly any function is like that. Most exercise a certain behavior on a subset of possible input values. The outputs also satisfy certain contracts, *i.e.* they also live in a certain subset of possible outputs. (Please mind that I don't mean strictly numerical ranges here -- it can be any subset of structured data.) As I already mentioned, the contracts have nothing to do with typing. You can use them for runtime type checks -- but that's a reduction of the concept to a very particular use case. Usually contracts read like this (from the numpy example linked in another message, https://www.numpy.org/devdocs/reference/generated/numpy.ndarray.transpose.ht... ): ndarray.transpose(**axes*) Returns a view of the array with axes transposed. *For a 1-D array, this has no effect. (To change between column and row vectors, first cast the 1-D array into a matrix object.) For a 2-D array, this is the usual matrix transpose. For an n-D array, if axes are given, their order indicates how the axes are permuted (see Examples). If axes are not provided and a.shape = (i[0], i[1], ... i[n-2], i[n-1]), then a.transpose().shape = (i[n-1], i[n-2], ... i[1], i[0]).* (emphasis mine) Mind the three postconditions (case 1D array, case 2D array, case N-D array). As for 4) reading the code, why not? "Use the source, Luke" is now a
Because it is hard and costs a lot of time. The point of encapsulating a function is that I as a user don't have to know its details of implementation and its wider dependencies in the implementation. Looking at the code is the tool of last resort to figure out the contracts. Imagine if you had to look at the implementation of numpy.transpose() to figure out what happens when transposing a N-D array. Cheers, Marko On Tue, 25 Sep 2018 at 13:13, Hugh Fisher <hugo.fisher@gmail.com> wrote:

Hi Hugh,
That was actually the option 2):
2) Write precondtions and postconditions in docstring of the method as human text.
The problem with text is that it is not verifiable and hence starts to "rot". Noticing that text is wrong involves much more developer time & attention than automatically verifying the formal contracts. In Python we can write something like: def foo(x): x.bar(y) What's the type of x? What's the type of y? What is the contract of bar? Don't know, don't care. x, or y, can be an instance, a class, a module, a proxy for a remote web service. The only "contract" is that object x will respond to message bar that takes one argument. Object x, do whatever you want with it. I still don't see how this is connected to contracts or how contracts play a role there? If foo can accept any x and return any result then there is *no *contract. But hardly any function is like that. Most exercise a certain behavior on a subset of possible input values. The outputs also satisfy certain contracts, *i.e.* they also live in a certain subset of possible outputs. (Please mind that I don't mean strictly numerical ranges here -- it can be any subset of structured data.) As I already mentioned, the contracts have nothing to do with typing. You can use them for runtime type checks -- but that's a reduction of the concept to a very particular use case. Usually contracts read like this (from the numpy example linked in another message, https://www.numpy.org/devdocs/reference/generated/numpy.ndarray.transpose.ht... ): ndarray.transpose(**axes*) Returns a view of the array with axes transposed. *For a 1-D array, this has no effect. (To change between column and row vectors, first cast the 1-D array into a matrix object.) For a 2-D array, this is the usual matrix transpose. For an n-D array, if axes are given, their order indicates how the axes are permuted (see Examples). If axes are not provided and a.shape = (i[0], i[1], ... i[n-2], i[n-1]), then a.transpose().shape = (i[n-1], i[n-2], ... i[1], i[0]).* (emphasis mine) Mind the three postconditions (case 1D array, case 2D array, case N-D array). As for 4) reading the code, why not? "Use the source, Luke" is now a
Because it is hard and costs a lot of time. The point of encapsulating a function is that I as a user don't have to know its details of implementation and its wider dependencies in the implementation. Looking at the code is the tool of last resort to figure out the contracts. Imagine if you had to look at the implementation of numpy.transpose() to figure out what happens when transposing a N-D array. Cheers, Marko On Tue, 25 Sep 2018 at 13:13, Hugh Fisher <hugo.fisher@gmail.com> wrote:
participants (2)
-
Hugh Fisher
-
Marko Ristin-Kaufmann