[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)