Would you use frozen-base

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 mail@joachim-breitner.de • http://www.joachim-breitner.de/ Jabber: nomeata@joachim-breitner.de • GPG-Key: 0xF0FBF51F Debian Developer: nomeata@debian.org

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

On Thu, Feb 26, 2015 at 9:14 PM, Roman Cheplyaka
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

On Thu, Feb 26, 2015 at 8:24 PM, Kim-Ee Yeoh
On Thu, Feb 26, 2015 at 9:14 PM, Roman Cheplyaka
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-...

On Fri, Feb 27, 2015 at 2:36 AM, Erik Hesselink
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].
[1] https://downloads.haskell.org/~ghc/7.10.1-rc1/docs/html/users_guide/release-...
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

This sounds like a dependency nightmare to me.
On Feb 26, 2015 8:36 AM, "Joachim Breitner"
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 mail@joachim-breitner.de • http://www.joachim-breitner.de/ Jabber: nomeata@joachim-breitner.de • GPG-Key: 0xF0FBF51F Debian Developer: nomeata@debian.org
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe

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: mail@joachim-breitner.de Homepage: http://www.joachim-breitner.de Jabber-ID: nomeata@joachim-breitner.de

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"
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 mail@joachim-breitner.de • http://www.joachim-breitner.de/ Jabber: nomeata@joachim-breitner.de • GPG-Key: 0xF0FBF51F Debian Developer: nomeata@debian.org
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe

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,

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 -- Joachim “nomeata” Breitner mail@joachim-breitner.de • http://www.joachim-breitner.de/ Jabber: nomeata@joachim-breitner.de • GPG-Key: 0xF0FBF51F Debian Developer: nomeata@debian.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 :).

-----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 alexander@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-----
participants (7)
-
Alexander Berntsen
-
Bardur Arantsson
-
David Feuer
-
Erik Hesselink
-
Joachim Breitner
-
Kim-Ee Yeoh
-
Roman Cheplyaka