Specifying dependencies on Haskell code

apfelmus apfelmus at quantentunnel.de
Fri May 2 05:27:37 EDT 2008

Duncan Coutts wrote:
> Thomas Schilling wrote:
>> For example, if we write a program that uses the function 'Foo.foo'  
>> contained in package 'foo' and we happen to have used 'foo-0.42' for  
>> testing of our program.  Then, given the knowledge that 'Foo.foo' was  
>> introduced in 'foo-0.23' and changed semantics in 'foo-2.0' then we  
>> know that 'foo >= 0.23 && < 2.0' is the correct and complete  
>> dependency description.

I would go even further and simply use "my program 'bar' compiles with 
foo-0.42" as dependency description. In other words, whether the package 
foo-0.23 can be used to supply this dependency or not will be determined 
when somebody else tries to compile Bar with it.

In both cases, the basic idea is that the library user should *not* 
think about library versions, he just uses the one that is in scope on 
his system. Figuring out which other versions can be substituted is the 
job of the library author. In other words, the burden of proof is 
shifted from the user ("will my program compile with foo-1.1?") to the 
author ("which versions of my library are compatible?"), where it belongs.

> However it would only help for the development _history_, we still have
> no solution for the problem of packages being renamed (or modules moving
> between packages) breaking other existing packages. Though similarly we
> have no solution to the problem of modules being renamed. Perhaps it's
> just that we have not done much module renaming recently so people don't
> see it as an issue.

With the approach above, it's possible to handle package/module 
renaming. For instance, if the package 'foo' is split into 'f-0.1' and 
'oo-0.1' at some point, we can still use the union of these two to 
fulfill the old dependency 'foo-0.42'.

In other words, the basic model is that a module/package like 'bar' with 
a dependency like 'foo-0.42' as just a function that maps a value of the 
same type (= export list) as 'foo-0.42' to another value (namely the set 
of exports of 'bar'). So, we can compile for instance

   bar (foo-0.42)


   bar (f-0.1 `union` oo-0.1)

Of course, the problems are

  1) specifying the types of the parameters,
  2) automatically choosing good parameters.

For 1), one could use a very detailed import list, but I think that this 
feels wrong. I mean, if I have to specify the imports myself, why did I 
import foo-0.42 in the first place? Put differently, when I say 'import 
Data.Map' I want to import both its implementation and the interface. 
So, I argue that the goal is to allow type specifications of the form 
'same type as foo-0.42'.

Problem 2) exists because if I have foo-0.5 on in scope on my system and 
a package lists foo-0.42 as a dependency, the compiler should somehow 
figure out that he can use foo-0.5 as argument. Of course, it will be 
tricky/impossible to figure out that  f-0.1 `union` oo-0.1  is a valid 
argument, too.

So, the task would be to develop a formalism, i.e. some kind of "lambda 
calculus for modules" that can handle problems 1) and 2). The formalism 
should be simple to understand and use yet powerful, just like our 
beloved lambda calculus.

A potential pitfall to any solution is that name and version number 
don't identify a compiled package uniquely! For instance,

   foo-0.3 (bytestring-1.1)

is very different from

   foo-0.3 (bytestring-1.2)

if foo exports the ByteString type. That's the diamond import problem. 
In other words,  foo-0.3  is always the same function, but the evaluated 
results are not.


More information about the Libraries mailing list