[HN Gopher] In Which I Claim Rich Hickey Is Wrong (2020)
       ___________________________________________________________________
        
       In Which I Claim Rich Hickey Is Wrong (2020)
        
       Author : Capricorn2481
       Score  : 17 points
       Date   : 2023-07-23 21:38 UTC (1 hours ago)
        
 (HTM) web link (blog.jonstodle.com)
 (TXT) w3m dump (blog.jonstodle.com)
        
       | XVincentX wrote:
       | The article is wrong. Rich Hickey is talking about the surface of
       | the function, interface. The implementation details do not matter
       | in this case.
        
         | riffraff wrote:
         | The article is claiming that the surface and the implementation
         | are related.
         | 
         | If client code passed a null value before, an error would have
         | occurred. Now it is handled with a default that the original
         | code was not accounting for, and this might be bad.
         | 
         | Thus the change in interface _is_ a change in behavior.
        
       | yakubin wrote:
       | I agree with Rich Hickey here and I think this is a flaw of sum
       | types compared to union types. If Option<T> was a union type, T
       | would be a subtype of Option<T> and those changes wouldn't be
       | breaking. When writing Rust, I find myself repeatedly writing
       | implementations of the From trait for enums, just because most of
       | the time I actually want union types, not sum types. I think sum
       | types should be built on top of union types by combining union
       | types with a "lexically-scoped newtype", if that makes sense
       | (i.e. they wouldn't be built in, they would be the result of two
       | orthogonal features, while allowing for other combinations as
       | well).
       | 
       | Edit: another nice consequence of this design would be None being
       | its own type, which can be transparently converted to an
       | Option<T> for any T, allowing for type inference to go in only
       | one direction, while still avoiding the boilerplate of
       | Option<T>::None. Unidirectional type inference would make for
       | more intelligible compiler errors. Full Hindley-Milner can get
       | confusing.
        
         | mjburgess wrote:
         | Whenever i design a lang, i just give option semantics to types
         | of the form `x|none`
        
         | kibwen wrote:
         | _> which can be transparently converted to an Option <T> for
         | any T, allowing for type inference to go in only one direction,
         | while still avoiding the boilerplate of Option<T>::None_
         | 
         | You don't need to qualify the None, the following works:
         | fn foo<T>(x: T) -> Option<T> {             None         }
        
         | lmm wrote:
         | Union types are horrendous in practice because they're non-
         | compositional. None|T is usually disjoint except when it isn't,
         | and it's really easy to forget that case and fail to test it
         | properly.
         | 
         | Having used Scala extensively, None as its own type is very
         | much a mistake; it's not a type that you ever want and it only
         | serves to get in the way.
        
       | patrickthebold wrote:
       | > You are changing the behavior of the function. That is a
       | breaking change.
       | 
       | That makes almost everything breaking.
        
         | nimih wrote:
         | Well, yes. Any time you change a function's behavior, you're
         | risking breaking the behavior of its callers. That's just the
         | nature of programming and doesn't seem like particularly
         | controversial statement, honestly.
        
           | Tcepsa wrote:
           | One of the big benefits of using functions is encapsulation:
           | the idea that you (as the caller) do not need to understand
           | how a function does what it does, you just need to know what
           | it does (perhaps along with some performance guarantees so
           | you know it's not going to do e.g. an O(n^2) operation).
           | Beyond that, I don't want to care and should not generally
           | have to care about what the function is doing. As long as I
           | get out (including side effects) what I expect based on what
           | I put in, the internal behavior of the function can change
           | any which way and I would not consider that breaking, because
           | my code--using that function--would still run just fine.
        
       | jhardy54 wrote:
       | Counter-point: consider the case where the body of the function
       | is only `return value`.
        
       | draw_down wrote:
       | No, he's right. he's speaking only of what is required and what
       | is provided, all else being equal. The what and the why are
       | different.
       | 
       | If the function started to provide different results for the same
       | arguments, that's different from what he's talking about and
       | would be a breaking change.
       | 
       | Worrying about the internals of the function is a violation of
       | encapsulation. We care what we provide and what we receive back.
        
         | one-more-minute wrote:
         | I mean, the article's logic is effectively that you can break
         | code without the type system helping or warning you. I don't
         | think Hickey would disagree with this - it's arguably a point
         | in his favour.
        
         | joeatwork wrote:
         | I think the poster means that adding null is a big, substantial
         | change to the interface - if you have a function that used to
         | not make sense with a null argument, and then it suddenly does
         | make sense with a null, then the function has changed in a way
         | observable to callers.
        
       | fiddlerwoaroof wrote:
       | The definition of breaking change here completely destroys any
       | idea of abstraction or bug fixes. If calling a function requires
       | knowing how the function is implemented, then the function should
       | not exist in the first case: the whole point of an abstraction is
       | that, if the use-site fulfills the precondition, it can assume
       | that the postcondition holds after it used the abstraction.
       | 
       | This means that relaxing the precondition is, by definition, not
       | a breaking change because the old inputs are a subset of the new
       | ones. Similarly, strengthening the postcondition isn't a breaking
       | change because the new outputs are a subset of the old ones.
        
       ___________________________________________________________________
       (page generated 2023-07-23 23:00 UTC)