[HN Gopher] Ad-hoc polymorphism erodes type-safety
       ___________________________________________________________________
        
       Ad-hoc polymorphism erodes type-safety
        
       Author : diarrhea
       Score  : 32 points
       Date   : 2023-08-31 19:59 UTC (3 hours ago)
        
 (HTM) web link (cs-syd.eu)
 (TXT) w3m dump (cs-syd.eu)
        
       | taeric wrote:
       | This feels less of polymorphism eroding type safety as it is
       | aliased terms eroding it. Stated differently,
       | programming/abstraction doesn't protect you from synonyms.
       | 
       | That is, it means different things to iterate over all allowed
       | values of a list than it does to iterate over the existence of
       | something. That they can be abstractly described in the same way
       | feels powerful, of course, but such is the case of powerful
       | tools?
        
         | tlb wrote:
         | Yes, and common synonyms like `length` can hose you when you
         | convert from a string to a list of strings. They shouldn't
         | really have the same name, especially in a Unicode world where
         | there are several different lengths for a string.
        
         | cmrdporcupine wrote:
         | Yes it's in the same category of "we know better now,
         | hopefully?" as people overloading the + operator for string
         | concatenation.
         | 
         | Refining for clarity of categories is hard. I'm not sure 'type
         | polymorphism' is the problem here as much as it is false
         | conceptual polymorphism.
        
         | cryptonector wrote:
         | Yes, this is about aliases. When you see `length` in a language
         | like Haskell you don't know what length exactly it refers to
         | without looking at the type of its argument. But if you see
         | `list_of_foo_length` then you know there can be only one
         | `list_of_foo_length` and where to look for it.
         | 
         | This makes things like source code indexers have to know how to
         | parse the language and how to understand its types even.
         | Compare to the lowly ctags/etags/cscope of C world! Clearly
         | monomorphic APIs are easier to deal with in some ways, though
         | they also clutter things up a bit.
        
       | legobmw99 wrote:
       | This is an interesting point but does rely on a rather narrow
       | definition of "type safety". By any definition I have used, the
       | code is type safe in those final examples. It has a bug, most
       | definitely, but it isn't type unsafe in the way that reading a
       | chunk of memory intended to hold type A as if it was holding a
       | type B is
        
         | tlb wrote:
         | Fully ad-hoc polymorphic languages like Smalltalk have this
         | property, but many prefer having the compiler catch more
         | errors.
        
       | dcsommer wrote:
       | I think this claim has some merit. Perhaps the only thing missing
       | (or perhaps intended to be self-evident) is that ad-hoc
       | polymorphism is also useful and pragmatically, this situation
       | doesn't come up too often. In particular, encapsulation reduces
       | the frequency that such errors can be introduced. In the
       | article's example, the `Setting` struct should (idiomatically)
       | have private elements and use a method in the struct's impl block
       | to expose the (optional) length of the field. And, to support the
       | author's point, you wouldn't use ad-hoc polymorphism for this
       | either.
       | 
       | The author's concern should be considered when introducing ad-hoc
       | polymorphism, especially when the type bounds are "loose" or
       | commonly defined by many types.
        
       | blkhp19 wrote:
       | In Swift, you shouldn't use Optional like this. You should
       | instead make a new type to represent exactly what parameter this
       | function expects:                 enum AllowList {         case
       | all         case some([ClientIdentifier])       }
        
       | kzrdude wrote:
       | Rust's iterability of Option is a good example for this because
       | (1) it has actually bit me, but it wasn't too bad and (2) it is
       | somewhere close to the border of where it's reasonable/useful to
       | treat it as a sequence or not. Logically it works, but Option is
       | not primarily a sequence.
       | 
       | Then the Rust code in the article funnily enough not use traits
       | for the `.iter()` call at all, that's just duck typing (no trait
       | for .iter()).
        
         | thinkharderdev wrote:
         | Yeah. it's bit awkward to make `Option` implement
         | `IntoIterator` and can certainly cause some weird and subtle
         | bugs. But on the other hand it does make a whole lot of things
         | work nicely. For example, you can turn a `Vec<Option<T>>` into
         | a `Vet<T>` trivially with
         | `foo.into_iter().flatten().collect()`. And generally it's nice
         | to have Option and Vec composable.
        
         | conaclos wrote:
         | I agree. Ironically the Rust standard library defines
         | iter::once using Option::Some [O].
         | 
         | [0] https://doc.rust-
         | lang.org/src/core/iter/sources/once.rs.html...
        
       | gpderetta wrote:
       | If instead of changing the allow list from a vector to optional
       | vector, it was changed, for whatever reason,to a vector of
       | vectors, wouldn't the monomorphic example compile and silently do
       | the wrong thing?
       | 
       | Even if you buy the thesis, the example is not terribly
       | convincing.
        
         | frenchy wrote:
         | This is a good point, even without polymorphism there are risky
         | refactors, but the polymorphism increases the number of
         | potential risky refactors.
         | 
         | On top of that, it might be a lot more obvious that turning a
         | vector into a vector of vectors might be risky, but that you
         | can iterate on Option won't be top-of-mind to everyone.
        
       | twiss wrote:
       | I think part (but not all) of the blame in this case should fall
       | to `length` and `.iter().count()` doing something rather
       | different for a list and an Option/Maybe type. Does taking the
       | length/iter of the latter really make sense? Or should one have
       | to do `option.is_some() as usize` instead, if that's really what
       | you want?
       | 
       | If the same method always did the same for all types that
       | implement it, this problem wouldn't occur. (That being said, I
       | understand that that might be too much to ask for / can't always
       | be avoided.)
        
         | chowells wrote:
         | But you see, this behavior _is_ the result of uniformly doing
         | the same thing. You 're asking for it to have special cases.
         | 
         | The signature "length :: Foldable f => f a -> Int" is pretty
         | specific. The polymorphism is parametric over `a' and ad-hoc
         | over `f'. It has to select its behavior based only on the
         | "container" type, rather than the "element" type. (The quoted
         | terms are sloppy, but better for intuition than talking about
         | deconstructing curried generative type constructor
         | application.)
         | 
         | Here's the thing about that. If your type is (Maybe (List
         | Foo)), the `f' variable (container type) gets matched with
         | `Maybe'. The `a' variable (element type) gets matched with
         | (List Foo). So the fact that there's a list involved gets
         | thrown away during instance resolution.
         | 
         | And that's a good thing, because it lets you reason about your
         | code's behavior uniformly. Just seeing the type of `length' is
         | enough to tell you it has to work this way. Any attempt to work
         | another way would fail to type-check.
         | 
         | --
         | 
         | Ultimately I think I slightly agree with the original post, but
         | I think it oversells the issue. Using ad-hoc polymorphism
         | inside a definition that doesn't expose that polymorphism
         | externally does allow a few more more incorrect
         | implementations. That is some form of erosion of type safety,
         | but a very minor one compared to what ad-hoc polymorphism
         | allows you to recover in safety when writing code that's useful
         | across many types.
        
           | twiss wrote:
           | > The signature "length :: Foldable f => f a -> Int" is
           | pretty specific.
           | 
           | That may be, but it isn't the best way to describe the type
           | of `length Maybe`, which behaves more like `f a -> Bool`,
           | since it can only return 0 or 1.
           | 
           | > You're asking for it to have special cases.
           | 
           | Not really, I'm just questioning whether the `Maybe` type
           | should implement `length`. Does it make sense to use an int
           | to describe whether it has a value? Does it make sense to
           | call that int its "length"? I would argue: sort of, but there
           | are better names for it, such as `boolToInt . isJust`.
        
             | davorak wrote:
             | > Not really, I'm just questioning whether the `Maybe` type
             | should implement `length`.
             | 
             | In this case no effort is made to make `Maybe` implement
             | `length` specifically other than making it implement
             | `Foldable`.                   ghci> :i Maybe         ...
             | instance Foldable Maybe -- Defined in 'Data.Foldable'
             | ...
             | 
             | and `length` is a method of the `Foldable` typeclass.
             | ghci> :i Foldable         ...         length :: t a -> Int
             | ...
             | 
             | https://hackage.haskell.org/package/base-4.18.0.0/docs/Prel
             | u...
             | 
             | length implementation for `Maybe`
             | 
             | https://hackage.haskell.org/package/base-4.18.0.0/docs/src/
             | D...
             | 
             | edit - accidentally submitted while part way through
             | editing.
        
         | anderskaseorg wrote:
         | It's useful for Option to be iterable so that it works with
         | things like .flat_map(), and it's useful to be able to count
         | from an arbitrary iterable.
         | 
         | .iter().count() is a contrived way to find the length of a Vec
         | though, since .len() is clearer and faster.
        
       | IshKebab wrote:
       | I mean, this is true. But traits are also very useful. Feels a
       | bit like saying "driving is dangerous; avoid driving where
       | possible".
       | 
       | Also "allow-list" is so gross. Maybe pick something less divisive
       | for future examples.
        
         | zzo38computer wrote:
         | > I mean, this is true. But traits are also very useful. Feels
         | a bit like saying "driving is dangerous; avoid driving where
         | possible".
         | 
         | I agree. Just because it can be misused does not mean it is no
         | good for anything. Note that you can declare types explicitly,
         | and is usually how I do when I write anything in Haskell
         | anyways, which makes the program clearer.
         | 
         | Also, changing the types after the rest of the program is
         | already written, is going to be a problem in any programming
         | language even without ad-hoc polymorphism, if you do not then
         | consider everything that needs to be changed due to that.
         | 
         | > Also "allow-list" is so gross. Maybe pick something less
         | divisive for future examples.
         | 
         | I agree with this, too. You can call it "allowed clients" or
         | "list of allowed clients" if you want to, which is better in my
         | opinion. (Simply "allow-list" does not even describe very well
         | what it is for; what is being allowed?)
        
         | nmjohn wrote:
         | > Also "allow-list" is so gross. Maybe pick something less
         | divisive for future examples.
         | 
         | What is gross or divisive about that?
        
           | twic wrote:
           | I'm guessing this is a reference to the movement by some
           | people to use that term instead of "whitelist" on the grounds
           | that the latter is racist. There's a division over whether
           | this is silly or not.
        
       | pxeger1 wrote:
       | The claim
       | 
       | > Using ad-hoc polymorphism can cause code to silently do the
       | wrong thing after a refactor because of a type error, in a way
       | that monomorphic code would not have
       | 
       | doesn't mean languages with ad-hoc polymorphism are less type-
       | safe overall. Ad-hoc polymorphism allows you to write stronger-
       | typed code more easily.
        
       ___________________________________________________________________
       (page generated 2023-08-31 23:01 UTC)