# Ad-hoc polymorphism erodes type-safety

**URL:** <https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464>\
**Category:** Links\
**Created:** [August 29, 2023, 2:28pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464 "2023-08-29T14:28:13Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![NorfairKing](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/norfairking/32/514_2.png) [@NorfairKing](https://discourse.haskell.org/u/NorfairKing)\
**Post date:** [August 29, 2023, 2:28pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/1 "2023-08-29T14:28:13Z")

</div>

[https://cs-syd.eu/posts/2023-08-25-ad-hoc-polymorphism-erodes-type-safety](https://cs-syd.eu/posts/2023-08-25-ad-hoc-polymorphism-erodes-type-safety)

---

<div class="post-metadata">

**Author:** ![simonpj](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/simonpj/32/890_2.png) [@simonpj](https://discourse.haskell.org/u/simonpj)\
**Post date:** [August 29, 2023, 2:50pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/2 "2023-08-29T14:50:12Z")

</div>

Interesting. See [Section 1.3 of GHC’s style guide](https://gitlab.haskell.org/ghc/ghc/-/wikis/commentary/coding-style#13-type-classes)

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [August 29, 2023, 3:03pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/3 "2023-08-29T15:03:53Z")

</div>

So what’s going on here is that `allowListSize` was morally

```haskell
allowListSize :: Foldable [] => Settings -> Int
allowListSize = length . settingAllowList

```

and it changed to

```haskell
allowListSize :: Foldable Maybe => Settings -> Int
allowListSize = length . settingAllowList

```

but because the type class constraint is automatically resolved we don’t get to see this change, become suspicious, and deal with the problem.

Since [FTP](https://wiki.haskell.org/Foldable_Traversable_In_Prelude) `length` is a particularly common culprit. I suggest using `length @[]` (or whatever) wherever you use it!

---

<div class="post-metadata">

**Author:** ![DavidB](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/davidb/32/3616_2.png) [@DavidB](https://discourse.haskell.org/u/DavidB)\
**Post date:** [August 29, 2023, 3:07pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/4 "2023-08-29T15:07:32Z")

</div>

In private conversations I usually call this the “invisible diff” problem, because this concerns the part of the diff that you usually don’t see when you review a PR on github or gitlab. But it is a diff which could theoretically be made visible by appropriate tooling: The diff only has to be done on the level of the desugared Core instead of on the textual diff.

If I take your example, then the following two versions of the function are generated during compile time (taken directly from [godbolt.org](http://godbolt.org)):

```haskell
-- Before
allowListSize :: Settings -> Int
allowListSize = . (length $fFoldableList) settingAllowList

```

vs

```haskell
-- After
allowListSize :: Settings -> Int
allowListSize = . (length $fFoldableMaybe) settingAllowList

```

It might be quite a bit of work to write this tooling, but it should theoretically be possible. You would only need both versions of the desugared Core available, and run a normal textual diff on that.

---

<div class="post-metadata">

**Author:** ![hellwolf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/hellwolf/32/3020_2.png) [@hellwolf](https://discourse.haskell.org/u/hellwolf)\
**Post date:** [August 29, 2023, 3:45pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/6 "2023-08-29T15:45:16Z")

</div>

What does type safety mean here?

I only smell the desired outcome that is “refactoring safe”… not sure you can get that from type system alone, unless using additional machinery that is dependent types (dependent haskell) or ~~gradual types~~ refinement types (liquid haskel)?

Edit: Or we do what all industrial programmers do, write functional test cases if your type system usage is not there yet to guarantee your spec.

---

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [August 29, 2023, 4:02pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/7 "2023-08-29T16:02:17Z")

</div>

> [@hellwolf](#):
>
> gradual types (liquid haskel)

Liquid Haskell uses liquid types which are a form of refinement types.

---

<div class="post-metadata">

**Author:** ![hellwolf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/hellwolf/32/3020_2.png) [@hellwolf](https://discourse.haskell.org/u/hellwolf)\
**Post date:** [August 29, 2023, 4:32pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/8 "2023-08-29T16:32:17Z")

</div>

> [@jaror](#):
>
> Liquid Haskell uses liquid types which are a form of refinement types.

Thanks for correcting, it was my confusion.

---

<div class="post-metadata">

**Author:** ![AntC2](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/antc2/32/1872_2.png) [@AntC2](https://discourse.haskell.org/u/AntC2)\
**Post date:** [August 30, 2023, 2:12am UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/9 "2023-08-30T02:12:52Z")

</div>

Thank you @NorfairKing: food for thought.

> [@tomjaguarpaw](#):
>
> Since [FTP](https://wiki.haskell.org/Foldable_Traversable_In_Prelude) `length` is a particularly common culprit.

Yes, FTP was controversial at the time (and probably still is). For example:

```haskell
Prelude> length (1, 2)
===> 1
Prelude> length (1, 2, 3)

<interactive>:5:1: error:
    * No instance for (Foldable ((,,) Integer Integer))
        arising from a use of `length'

```

But you could define `instance Foldable` for all sorts of tuples.

IMO `length` should have remained restricted to lists (all those tutorials!); FTP should have introduced a new `scaryOverloadableLength` for `Foldable`s.

So are there examples of the dangers of refactoring misleadingly failing to throw type errors, other than with `length`? The article says

> can occur for with every class/trait that has more than one instance/impl.

Ok. Are there classes other than `Foldable` with instances for so many generic data container types?

---

<div class="post-metadata">

**Author:** ![ChShersh](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/chshersh/32/2551_2.png) [@ChShersh](https://discourse.haskell.org/u/ChShersh)\
**Post date:** [August 30, 2023, 9:51am UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/10 "2023-08-30T09:51:00Z")

</div>

In my years of experience, I developed a rule of thumb of when an abstraction erodes type-safety.

I call typeclasses like `Foldable` _structure-oblivious_ because they don’t preserve the structure of the argument in their result. That’s why they’re potentially dangerous because if you add more layers on top of your argument you have no way to leverage the type checker to catch potentially wrong behaviour earlier. So you must rely on other tools to verify your code (tests, LiquidHaskell, manual code review, etc.).

Examples of _structure-oblivious_ abstractions:

```haskell
length :: Foldable f => f a -> Int
show :: Show a => a -> String
toJSON :: ToJSON a => a -> Value

```

* * *

In contrast, some typeclasses are _structure-preserving_. The type of the structure is preserved in the result. That’s why all types propagate. If you change a value from `settingAllowList :: [ClientIdentifier]` to `settingAllowList :: Maybe [ClientIdentifier]`, functions like `fmap fromClientIdentifier settingAllowList` don’t compile anymore.

Examples of _structure-preserving_ abstractions:

```haskell
(<>) :: Semigroup a => a -> a -> a
fmap :: Functor f => (a -> b) -> f a -> f b

```

* * *

I noticed, when there’s a bug due to a usage of a typeclass, it’s usually because the typeclass is _structure-oblivious_.

---

<div class="post-metadata">

**Author:** ![chreekat](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/chreekat/32/2669_2.png) [@chreekat](https://discourse.haskell.org/u/chreekat)\
**Post date:** [August 30, 2023, 11:45am UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/11 "2023-08-30T11:45:30Z")

</div>

> [@ChShersh](#):
>
> I noticed, when there’s a bug due to a usage of a typeclass, it’s usually because the typeclass is _structure-oblivious_.

Nice, and I can intuit why this might be the case! Sounds like the beginning of a HLint rule…

---

<div class="post-metadata">

**Author:** ![chrisdone](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/chrisdone/32/1408_2.png) [@chrisdone](https://discourse.haskell.org/u/chrisdone)\
**Post date:** [August 31, 2023, 8:46pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/12 "2023-08-31T20:46:46Z")

</div>

I suppose GHC could warn about any method call which is not fully polymorphic and the definition of which has a given type variable (mentioned in the class head) on the left hand side of the arrow but not on the right hand side. On a superficial reading, that seems to define these structure discarding methods. Is there more to the story?

---

<div class="post-metadata">

**Author:** ![rhendric](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/rhendric/32/2689_2.png) [@rhendric](https://discourse.haskell.org/u/rhendric)\
**Post date:** [August 31, 2023, 9:46pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/13 "2023-08-31T21:46:20Z")

</div>

How does HLint or the compiler determine the difference between

```haskell
middle of some code somewhere (length tree) more code

```

where `tree` is a particular `Foldable` data structure, and

```haskell
lengthTree :: Tree a -> Int
lengthTree = length

```

which I infer is the ‘responsible’ way to use this sort of method?

---

<div class="post-metadata">

**Author:** ![michaelpj](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/michaelpj/32/417_2.png) [@michaelpj](https://discourse.haskell.org/u/michaelpj)\
**Post date:** [August 31, 2023, 10:19pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/14 "2023-08-31T22:19:27Z")

</div>

I would be interested to know how many people have actually had a bug caused by this (and of those, in how many cases it was exactly that one `Foldable` tuple footgun). I don’t think I ever have.

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [September 1, 2023, 6:45am UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/15 "2023-09-01T06:45:06Z")

</div>

> [@rhendric](#):
>
> How does HLint or the compiler determine the difference

HLint can’t. It doesn’t have type information. GHC can because it does. GHC case can see that in the first example `length` is used at polymorphic type (I presume that’s what you meant) and `lengthTree` is used an monomorphic type.

In fact, [`stan`](https://github.com/kowainik/stan) already has a rule for this. (And now for your regular PSA that `stan` doesn’t support GHC 9.x but I have [a branch](https://github.com/kowainik/stan/issues/423#issuecomment-1264348102) that adds support up to GHC 9.4).

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [September 1, 2023, 6:45am UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/16 "2023-09-01T06:45:46Z")

</div>

> know how many people have actually had a bug caused by this

I have, a few times over the years, I think.

---

<div class="post-metadata">

**Author:** ![rhendric](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/rhendric/32/2689_2.png) [@rhendric](https://discourse.haskell.org/u/rhendric)\
**Post date:** [September 1, 2023, 6:49am UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/17 "2023-09-01T06:49:40Z")

</div>

I might be misunderstanding, but isn’t the original issue that `length` was used on one monomorphic type (`[ClientIdentifier]`), and then the code changed so that it was being used on another monomorphic type (`Maybe [ClientIdentifier]`) without the programmer realizing? If monomorphic-type-good is the rule, then neither of these would be flagged. If polymorphic-type-good, then how do you ever use a polymorphic function without generating warnings (this is what I was trying to ask in my previous post)?

---

<div class="post-metadata">

**Author:** ![tomjaguarpaw](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tomjaguarpaw/32/1230_2.png) [@tomjaguarpaw](https://discourse.haskell.org/u/tomjaguarpaw)\
**Post date:** [September 1, 2023, 6:53am UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/18 "2023-09-01T06:53:38Z")

</div>

Oh, I see, you mean how does it tell that `lengthTree` is a “good” usage? And it’s more a question about the semantics of “good” usage than the mechanics of how tooling detects it?

(I should add a correction about `stan`: I think what it detects is certain “bad” monomorphic usages, over `(,) a` particularly, and probably others.)

---

<div class="post-metadata">

**Author:** ![rhendric](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/rhendric/32/2689_2.png) [@rhendric](https://discourse.haskell.org/u/rhendric)\
**Post date:** [September 1, 2023, 7:01am UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/19 "2023-09-01T07:01:47Z")

</div>

Yeah, I guess it’s a question about semantics. I was responding specifically to

> [@chrisdone](#):
>
> I suppose GHC could warn about any method call which is not fully polymorphic and the definition of which has a given type variable (mentioned in the class head) on the left hand side of the arrow but not on the right hand side. On a superficial reading, that seems to define these structure discarding methods. Is there more to the story?

And I think either there is more to the story or I just don’t understand what’s being proposed, because sometimes you’re going to want to use a function like `length` on a not-fully-polymorphic type, so what’s the plan for allowing that when it’s ‘correct’, and what does ‘correct’ even mean?

---

<div class="post-metadata">

**Author:** ![Ambrose](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ambrose/32/5672_2.png) [@Ambrose](https://discourse.haskell.org/u/Ambrose)\
**Post date:** [September 1, 2023, 4:37pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/21 "2023-09-01T16:37:13Z")

</div>

catamorphism considered harmful

---

<div class="post-metadata">

**Author:** ![chrisdone](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/chrisdone/32/1408_2.png) [@chrisdone](https://discourse.haskell.org/u/chrisdone)\
**Post date:** [September 1, 2023, 8:00pm UTC](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464/22 "2023-09-01T20:00:31Z")

</div>

I think the proposal of the post was that you’d use Tree.length or List.length rather than Foldable.length when you know you’re working with a tree or list, for example.

[Next page](https://discourse.haskell.org/t/ad-hoc-polymorphism-erodes-type-safety/7464.md?page=2)
