Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Package versioning policy - Harmless type changes?

Tags:

haskell

cabal

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?

like image 812
Heinrich Apfelmus Avatar asked Sep 22 '11 07:09

Heinrich Apfelmus


2 Answers

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.

like image 185
Joachim Breitner Avatar answered Nov 09 '22 06:11

Joachim Breitner


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.

like image 28
Dan Burton Avatar answered Nov 09 '22 07:11

Dan Burton