5 ms·
My issue is that the author doesn’t seem to know what a breaking change is. Spec itself is annoying to me for a couple reasons, but this isn’t some novel insigh
by fiddlerwoaroof 3y ago
My issue is that the author doesn’t seem to know what a breaking change is. Spec itself is annoying to me for a couple reasons, but this isn’t some novel insight of Rich Hickey’s: the claim about relaxing preconditions and strengthening postconditions is in Bertrand Meyer’s _Object Oriented Software Construction_ and is a relatively basic claim in texts about what it means for software to be correct.
- Capricorn2481 3y agoI would agree the author was wrong, and I would hazard a guess that you're a lot more well read than most of us on this kinds of stuff, because this talk is widely seen as an insightful. Is there a language that you feel solves this problem better than Spec could ever hope to?
- deleted 3y ago[deleted]
- kazinator 3y agoExtending a function to accept null, under dynamic typing, can break a program which depends on the documented behavior of that function having thrown an exception for that case. (defun strlen (s) (assert (stringp s)) (len s)) (defun map-len-test () (mapcar (lambda (arg) (ignerr (strlen arg))) '("abc" nil "defg"))) (pprinl (map-len-test)) (defun strlen (s) (if (null s) 0 (len s))) (pprinl (map-len-test)) Output: (3 nil 4) (3 0 4) The caller had one idea of substituting a value in the null argument case. The function changed behavior, implementing a different idea. Only in a statically typed language in which the call with nil is impossible, and which provides no dynamic access to the compiler, can we be 100% confident in saying that extending the function to support nil is a non-breaking change. If the is impossible, then the function has no behavior for that case; a program invoking that case doesn't exist in a translated, executable form and so there is nothing to break as far as the program is concerned. It's possible for another program to break: a compilation test case which validates that code which calls that function with nil cannot be compiled. If the language supports dynamic access to the compiler, then an application can be written which can behave differently due to the change: an application which dynamically compiles a call to the function and now gets a valid result (compiled code) rather than an error. In the dynamic world, it's a non-breaking change for applications that wisely don't rely on exceptions being thrown on bad inputs, and instead handle those themselves. If you're changing the function, you cannot know that is not the case; at beast you can decide not to care. Not caring is not the same as knowing.
- fiddlerwoaroof 3y ago> Extending a function to accept null, under dynamic typing, can break a program which depends on the documented behavior of that function having thrown an exception for that case. This just means that the preconditions are “the value is not null” even if there are no types to capture that precondition. I was pretty careful not to use the word “type” in the comment you’re replying to.
- kazinator 3y agoThe problem is that in fact there are no preconditions; the function can be called unconditionally using any value as an argument. It throws for some values, and any change in which ones is a visible change. Programmers will make code dependent on anything that is visible in an API, even if the specification tells them not to, an ISO standard tells them not to and even if their mother tells them not to. Here, the spec may even be saying: if you call this function with a non-string, it throws an exception.
- fiddlerwoaroof 3y ago> The problem is that in fact there are no preconditions; the function can be called unconditionally using any value as an argument. There are no _checked_ preconditions. It’s only meaningful to talk about the correctness of a program relative to a specification of its behavior and a specification needs to specify preconditions and postconditions. So, if there are no preconditions, any behavior of the function being called is as correct as any other behavior. The fact that some programming languages allows the callers of a function to pass values that don’t satisfy the spec is irrelevant to the point I’m making.
- kazinator 3y agoThe function checks for a type with assert: (assert (stringp arg)). But that check is a documented behavior which generates a predictable exception that the caller can detect and so can tell when that is suddenly missing. No matter what consequence you give to the failed check, something can break when the check is taken away. We could have a True(r) Scotsman's checked precondition like this (unless (stringp arg) (abort)) ;; bail the entire process image with abnormal status If we check it like that, nobody will try to depend on the behavior within an application, in the way that my test program did. It can still be a breaking change if the function is written that way, and then changed so the abort is taken away. Because the application can do this: (defun my-stupid-abort () (strlen nil)) ;; this calls abort! So now if the program relies on (my-stupid-abort) to abort, that will break; suddenly that function call returns zero and the process keeps going. You really need: ;; ... ffi definitions here ... (unless (stringp arg) (acpi-power-off)) But again, dumb program can depend on (strlen nil) to power of the machine, which could be part of some critical system that breaks as a result of it not powering off. Only a compile time check can do it, by preventing the program from existing. A program that never compiled and therefore was never deployed to its installation will never break.