[Haskell-cafe] Would you use frozen-base

39 views
Skip to first unread message

Joachim Breitner

unread,
Feb 26, 2015, 8:36:19 AM2/26/15
to Haskell Cafe
Hi,

= Introduction (can be skipped) =

Recently, I find myself often worried about stability of our ecosystem –
may it be the FTP discussion, or the proposed¹ removal of functions you
shouldn’t use, like fromJust. While all that makes our language and base
libraries nicer, it also means that my chances that code from 3 or 4
years ago will compile without adjustment on newer compilers are quite
low.

Then I remember that a perl based webpage that I wrote 12 years ago just
keeps working, even as I upgrade perl with every Debian stable release,
and I’m happy about that. It makes me wonder if I am ever going to
regret using Haskell because there, I’d have to decide whether I want to
invest time to upgrade my code or simply stop using it.

I definitely do not want to stop Haskell from evolving. Maybe we can
make a more stable Haskell experience opt-in? This is one step in that
direction, solving (as always) only parts of the problem.

= The problem =

One problem is that our central library "base" is both an implementation
and an interface. As the implementation is tied to the compiler, and the
interface comes with the implementation, you cannot upgrade one without
the other.

= My solution: frozen-base =

My solution would be to provide a new library, let’s call it
frozen-base, which decouples the interface (frozen-base) from the
implementation (still base).

We would start with one particular version of base, say, 4.6. With
GHC-7.6 (which comes with base 4.6), frozen-base would simply re-export
its modules². On newer compilers and newer versions of base, it would
re-create the old interface using CPP. So if your program depends on
frozen-base only, you can expect it to compile with newer versions of
GHC – achievement unlocked.

= How to get new features, then? =

But does that mean that you do not get to use great new functionality in
later versions of base, such as Data.Bool.bool? No: When a new version
of base gets released, and a module gets changed, than frozen-base will
ship a _new_ module, named
Data.Bool1
that matches that interface. The next change in base causes yet another
module to be created, i.e.
Data.Bool2
So if you need to use Data.Bool.bool, in one of your modules, you simply
change the import from "import Data.Bool" to "import Data.Bool2", adjust
your code to the new interface, and are ready to go. Note that you only
had to adjust to the changes in Data.Bool, nothing else. Note that you
also had to touch only a single module in your program, the others still
use whatever interface they were using.

Your dependency on frozen-base would have to indicate a lower version
bound, i.e. specify the lowest version that has all the Data.Bool<n>
variants that you desire, but – by design – never an upper version.³

= When does it not work? =

Of course, the promises by froze-base are not absolute: There are
changes in base that frozen-base cannot protect you against: Data type
changes, some type class changes, type class instances. I don’t have any
great ideas here, but maybe they are rare enough so that frozen-base is
still useful.

= What about the Prelude? =

I did not talk about the Prelude. There probably is not a good solution,
and I’d simply leave the Prelude frozen. Maybe later GHC has a nice way
to specifying which Prelude to use in a certain module, then you could
use "Prelude1", "Prelude2" etc. in the same manner.

= How does it related to base-compat? =

The idea is similar to what base-compat provides, but the other way
around: base-compat allows you to use the latest features of base on
older compilers, while frozen-base would allow you to use the API of old
base versions on newer systems.

Frozen-base might subsume base-compat by providing, say Data.Bool2 with
the API from base-4.8 also on systems with base-4.6.

= How does it related to the split base proposal =

Depending on how exactly base is being reconstructed, it might become a
pure interface package on its own, or at least independent from
compiler-specific bits. Then it might be feasible that, say, after the
release of ghc-7.10 there is a new upload of base-4.6.0.n that provides
the 4.6 API, but compiles on ghc-7.10. This way, if you depend on
base == 4.6.*
you could still expect your program to work. If that happens, and also
cabal would learn that a plan with different versions of a interface
only library like base are not a problem, frozen-base might be
obsoleted.

See https://ghc.haskell.org/trac/ghc/wiki/SplitBase for more on that.


= No Conclusion and lots of future work =

So what do you think? Would you be interested in using this? Do you have
reasons to believe that this will not work as nicely as I think i could?



Greetings,
Joachim




¹ but rejected, it seems
² or a sensible subset; I could imagine not exporting certain .Internal
modules and things like OldTypeable that are about compatibility
themselves.
³ or just one on the very major version number (frozen-base < 2), which
to allow for exceptional breaking changes – a redesign of the module
naming scheme for example.

--
Joachim “nomeata” Breitner
ma...@joachim-breitner.dehttp://www.joachim-breitner.de/
Jabber: nom...@joachim-breitner.de • GPG-Key: 0xF0FBF51F
Debian Developer: nom...@debian.org
signature.asc

Roman Cheplyaka

unread,
Feb 26, 2015, 9:14:12 AM2/26/15
to Joachim Breitner, Haskell Cafe
I don't think frozen-base by itself is enough to achieve the goal of
compiling n-year-old code without modifications.

Just a couple of examples that jump to mind:

- the recent change to require extensions for inferred types
- AMP (e.g. your code had a monad defined without an applicative instance)

Roman

signature.asc

David Feuer

unread,
Feb 26, 2015, 9:17:50 AM2/26/15
to Joachim Breitner, haskel...@haskell.org

This sounds like a dependency nightmare to me.

_______________________________________________
Haskell-Cafe mailing list
Haskel...@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe

David Feuer

unread,
Feb 26, 2015, 9:21:45 AM2/26/15
to Joachim Breitner, haskel...@haskell.org

I believe that a way to avoid orphan instances without needing huge modules (such as my instance declaration idea) would help a lot in this regard, long term—the smaller modules are, the less trouble changes cause and the better a system you propose can work.

On Feb 26, 2015 8:36 AM, "Joachim Breitner" <ma...@joachim-breitner.de> wrote:

Joachim Breitner

unread,
Feb 26, 2015, 9:24:23 AM2/26/15
to haskel...@haskell.org
Hi,

Am Donnerstag, den 26.02.2015, 09:17 -0500 schrieb David Feuer:
> This sounds like a dependency nightmare to me.

care to elaborate? A nightmare for whom?


Greetings,
Joachim
>

--
Joachim Breitner
e-Mail: ma...@joachim-breitner.de
Homepage: http://www.joachim-breitner.de
Jabber-ID: nom...@joachim-breitner.de
signature.asc

Kim-Ee Yeoh

unread,
Feb 26, 2015, 2:25:38 PM2/26/15
to Roman Cheplyaka, Joachim Breitner, Haskell Cafe
On Thu, Feb 26, 2015 at 9:14 PM, Roman Cheplyaka <ro...@ro-che.info> wrote:
I don't think frozen-base by itself is enough to achieve the goal of
compiling n-year-old code without modifications.

I second Joachim's concerns about stability. While not completely au fait with the technicalities of frozen-base, it does seem to me that something more is needed.


Just a couple of examples that jump to mind:

- the recent change to require extensions for inferred types

I did a search on ghc tickets and while a few seems to fit the bill, I don't know what this refers to.
 
- AMP (e.g. your code had a monad defined without an applicative instance)

As a first pass, punt on this?


-- Kim-Ee

Erik Hesselink

unread,
Feb 26, 2015, 2:37:15 PM2/26/15
to Kim-Ee Yeoh, Joachim Breitner, Haskell Cafe
On Thu, Feb 26, 2015 at 8:24 PM, Kim-Ee Yeoh <k...@atamo.com> wrote:
>
> On Thu, Feb 26, 2015 at 9:14 PM, Roman Cheplyaka <ro...@ro-che.info> wrote:
>> Just a couple of examples that jump to mind:
>>
>> - the recent change to require extensions for inferred types
>
> I did a search on ghc tickets and while a few seems to fit the bill, I don't
> know what this refers to.

GHC 7.10 requires extensions like FlexibleContexts on inferred
signatures if writing down the signature would need the extension. See
the top bullet point here [1].

Erik

[1] https://downloads.haskell.org/~ghc/7.10.1-rc1/docs/html/users_guide/release-7-10-1.html#idp5770992

Kim-Ee Yeoh

unread,
Feb 26, 2015, 2:48:01 PM2/26/15
to Erik Hesselink, Joachim Breitner, Haskell Cafe
On Fri, Feb 27, 2015 at 2:36 AM, Erik Hesselink <hess...@gmail.com> wrote:
GHC 7.10 requires extensions like FlexibleContexts on inferred
signatures if writing down the signature would need the extension. See
the top bullet point here [1].

Thank you, Erik. Something that comes to mind immediately is a pragma to turn it off. You don't happen to know the patch this was in, do you?

-- Kim-Ee

Bardur Arantsson

unread,
Feb 26, 2015, 2:51:20 PM2/26/15
to haskel...@haskell.org
On 26-02-2015 14:36, Joachim Breitner wrote:
> Hi,
[--snip--]
>
> But does that mean that you do not get to use great new functionality in
> later versions of base, such as Data.Bool.bool? No: When a new version
> of base gets released, and a module gets changed, than frozen-base will
> ship a _new_ module, named
> Data.Bool1
> that matches that interface. The next change in base causes yet another
> module to be created, i.e.
> Data.Bool2
> So if you need to use Data.Bool.bool, in one of your modules, you simply
> change the import from "import Data.Bool" to "import Data.Bool2", adjust
> your code to the new interface, and are ready to go. Note that you only
> had to adjust to the changes in Data.Bool, nothing else. Note that you
> also had to touch only a single module in your program, the others still
> use whatever interface they were using.

Having actually worked with code using this approach to versioning, I
think I can safely say that it is absolute hell in practice -- way worse
than sprinkling a few CPP directives here and there. Granted, in my
particular case data structures were also versioned thusly, and so you'd
end up with reams of boilerplate (but only sometimes boilerplate!)
Bool1<->Bool2 conversion code. Similar problems would ensue if semantics
of Foo1.frobnicate() and Foo2.frobnicate() were subtly different, but
you'd happened to call the wrong one. Not to mention all the namespace
pollution if you need to access both Foo1 and Foo2 in the same module.
There's also the mental overhead of having to be aware of all the Foo1,
Foo2, ... modules and the differences between them. (There's probably a
lot more pain that I've just repressed, but that's just off the top of
my head.)

The proper solution to this problem (as with so many things) is another
layer of indirection, namely a proper way to separate API and
implementation. (Aka "Backpack" or similar.)

Regards,

Joachim Breitner

unread,
Feb 26, 2015, 6:07:50 PM2/26/15
to haskel...@haskell.org
Hi,

Am Donnerstag, den 26.02.2015, 20:50 +0100 schrieb Bardur Arantsson:
> Having actually worked with code using this approach to versioning, I
> think I can safely say that it is absolute hell in practice -- way worse
> than sprinkling a few CPP directives here and there.

Interesting, and good to hear about that!

> Granted, in my
> particular case data structures were also versioned thusly, and so you'd
> end up with reams of boilerplate (but only sometimes boilerplate!)
> Bool1<->Bool2 conversion code.

The way I imagine it to work, this would happen here, as the actual data
type will always be the one from the current system’s base.

> Similar problems would ensue if semantics
> of Foo1.frobnicate() and Foo2.frobnicate() were subtly different, but
> you'd happened to call the wrong one. Not to mention all the namespace
> pollution if you need to access both Foo1 and Foo2 in the same module.

Well, that should not ever need happen (comparable to how today, you
never access base-4.6:Foo and base-4.7:Foo in the same package – I
hope).

> There's also the mental overhead of having to be aware of all the Foo1,
> Foo2, ... modules and the differences between them. (There's probably a
> lot more pain that I've just repressed, but that's just off the top of
> my head.)
>
> The proper solution to this problem (as with so many things) is another
> layer of indirection, namely a proper way to separate API and
> implementation. (Aka "Backpack" or similar.)

But that is precisely what frozen-base is trying to provide – just APIs,
no implementations. Note that it should merely re-export stuff from base
(or maybe base-compat or similar), and simply guarantee that the set of
exported functions of one particular module does not change any more.

Greetings,
Joachim
signature.asc

Bardur Arantsson

unread,
Feb 28, 2015, 8:25:12 AM2/28/15
to haskel...@haskell.org
On 27-02-2015 00:07, Joachim Breitner wrote:

>> The proper solution to this problem (as with so many things) is another
>> layer of indirection, namely a proper way to separate API and
>> implementation. (Aka "Backpack" or similar.)
>
> But that is precisely what frozen-base is trying to provide – just APIs,
> no implementations. Note that it should merely re-export stuff from base
> (or maybe base-compat or similar), and simply guarantee that the set of
> exported functions of one particular module does not change any more.

Indeed, but (usually) it's no good if you can see the layer of
indirection :).

Alexander Berntsen

unread,
Mar 2, 2015, 6:18:53 AM3/2/15
to haskel...@haskell.org
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

On 26/02/15 20:50, Bardur Arantsson wrote:
> The proper solution to this problem (as with so many things) is
> another layer of indirection, namely a proper way to separate API
> and implementation. (Aka "Backpack" or similar.)
I'd prefer to see work going into Backpack or similar rather than
frozen-base. I share the concerns expressed by Bardur and David. (Of
course I don't want to discourage work on frozen-base altogether, as
I'm sure it could be an interesting experiment if nothing more.)

- --
Alexander
alex...@plaimi.net
https://secure.plaimi.net/~alexander
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iF4EAREIAAYFAlT0RxEACgkQRtClrXBQc7VZPQD/f/NxeQh4uo2HP/v+oJJ/bMOA
T1EGkF1LbBM0/J1Ykk0A/0rosssIfCCYAFuOZgI2BGMei0CSKPt23zweBrqYkfB+
=OymM
-----END PGP SIGNATURE-----
Reply all
Reply to author
Forward
0 new messages