The package versioning policy specifies that changing the type of any entity makes a change of the B number in A.B.C necessary.
However, it seems to me that some type changes don't break dependent code. In particular, consider the following example, where I drop a Typeable
class constraint:
- foo :: Typeable a => AddHandler a -> NetworkDescription (Event a)
+ foo :: AddHandler a -> NetworkDescription (Event a)
So, my question is:
Can removing a type class constraint on a function break dependent code? Should I change the B number or just the C in version A.B.C when introducing this change?
I have replied on -cafe, but I’ll also put my answer here:
You should bump the C number. The PVP, rule 2, specifies that an API addition implies that the C part of the version is to be increased. Removing a constraint behaves like adding a new function: Code that worked before continues to work, but code written against the new API might not work against the old one.
So if a programmer develops code against version 0.1.2 of foo, he’d specify foo >= 0.1.2 && < 0.2
as the requirement. He does not expect his code to work against foo-0.1.1. This works fine with removing the constraint.
Can removing a type class constraint on a function break dependent code?
No, I don't believe so. Here's my reasoning. It's common to think of constraints as an additional function argument: a typeclass dictionary which is passed implicitly. You can think slightly more general than that, and imagine that constraints are a record of evidences which is passed implicitly. Thus, the type Foo
can be thought of as {} -> Foo
, where {}
indicates an empty record of implicit evidences. It is irrelevant whether Foo
itself is a function or not.
Now, suppose that our API promises to deliver something of type
{SomeConstraint} -> Foo
But what we actually deliver is
{} -> Foo
Well, record subtyping tells us that
{SomeConstraint} <: {}
therefore function subtyping tells us that
({} -> Foo) <: ({SomeConstraint} -> Foo)
therefore, anywhere in someone's program that we find a hole with the shape {SomeConstraint} -> Foo
, we can plug in a {} -> Foo
. In other words, nothing breaks. The user gives us evidence that SomeConstraint
is satisfied, and we just ignore it.
At first I thought that weird corner cases in type inference might cause problems, but I cannot think of any examples where this is actually the case. So this thought was proven wrong by exhaustion of imagination.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With