[prev in list] [next in list] [prev in thread] [next in thread] 

List:       haskell-cafe
Subject:    Re: [Haskell-cafe] The amount of CPP we have to use is getting out of hand
From:       Nicola Gigante <nicola.gigante () gmail ! com>
Date:       2015-01-09 17:59:18
Message-ID: 201103D2-E172-4BCA-B4A0-B6A52A1F75CE () gmail ! com
[Download RAW message or body]

[Attachment #2 (multipart/alternative)]


> Il giorno 09/gen/2015, alle ore 14:55, Johan Tibell <johan.tibell@gmail.com> ha \
> scritto: 
> Hi,
> 
> (This was initially written as a Google+ post, but I'm reposting it here to raise \
> awareness of the issue.) 
> The amount of CPP we have to use in Haskell is getting a bit out of hand. Here are \
> the number of modules, per library, that use CPP for some of the libraries I \
> maintain: 
> containers 18/18
> hashable 4/5
> unordered-containers 6/9
> network 3/7
> cassava 4/16
> cabal/cabal-install 13/75
> cabal/Cabal 7/78
> ekg 1/15
> 
> If this doesn't look like a lot to you (I hope it does!) consider than some \
> languages don't use CPP at all (e.g. Java). 
> CPP really sucks from a maintenance perspective:
> 
> * It's not Haskell, but this bizarre string concatenation language.
> * The code is harder to read, bitrots more easily, and is harder to test.
> * The code can't be compiled without using Cabal (which generates some of the CPP \
> macros for us.) This hurts e.g. ad-hoc testing/benchmarking. 
> There are a couple of reasons we use CPP, but the main one is breaking changes in \
> GHC and libraries we depend on. We need to reduce these kind of breakages in the \
> future. Dealing with breakages and maintaining the resulting CPP-ed code is costing \
> us time we could spend on other things, such as improving our libraries or writing \
> new ones. I for one would like to get on with writing applications instead of \
> spending time on run-of-the-mill libraries. 
> Often these breaking changes are done in the name of "making things cleaner". \
> Breaking changes, no matter how well-intended, doesn't make code cleaner, it makes \
> it less clean*. Users end up having to use both the old "unclean" API and the new \
> "clean" API. 
> The right way to move to evolve an new API is to add new functions and data types, \
> not modify old ones, whenever possible. 
> * It takes about 3 major GHC releases (~3 years) before you can remove the CPP, but \
> since new things keep breaking all the time you always have a considerable amount \
> of CPP. 

Hi

I'm an outsider so this could probably sound ingenuous but,
why not thinking about an in-language feature to solve the
problems addressed by CPP?

I think these all fall into:
Enable some top-level declaration only if the XYZ feature is
available.

This feature should allow the user to specify different definitions
of the same symbol depending on the availability of compiler
features but also _modules_ features.

So if I can declare and export a newFunc function from my module 
only if DataKinds is supported, I can do it explicitly instead of
relying on GHC version X.Y.Z. On the other hand, the users of
my module can decide if they want to compile some code depending
on the fact that my module exports the function or not.

This should not be limited to "the module exports the function". Other
types of "features" could be tested over, and modules should be
able to declare the new features added which deserve to be tested
in this way. For example, if in the 2.0 version of my module I've
increased the laziness of my data structure, I can export the feature
"MyModule.myFunc is lazy" (encoded in some way). Then the
user can decide which implementation of its algorithm to use depending
on this.

I think a system like this should solve the majority of maintenance burden
because:
- Dependencies on libraries features are explicit and the version
  numbers needed to support them can be inferred by cabal.
  For example cabal could support a syntax like containers(with:foo_is_lazy)
  instead of containers >= x.y.z
- GHC can automatically warn about features that are supported by all the 
  currently supported versions of GHC so that the checks can be removed.
- Code is more testable because the test suite could run tests multiple times,
  each time "faking" the availability of certain features, with the GHC support 
  of a "fake old version mode" where it has simply to pretend to not know
  the existence of a certain extension (not at all a "compatibility mode", to be
  clear). As for library features, integrating with cabal sandboxes one could 
  automatically switch library versions to run the test with.
- Other?

I repeat: I'm an outsider of the world of maintenance of the haskell packages,
so I could be missing something obvious. Hope this can be useful though.


> — Johan

Bye,
Nicola


[Attachment #5 (unknown)]

<html><head><meta http-equiv="Content-Type" content="text/html \
charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; \
-webkit-line-break: after-white-space;" class=""><br class=""><div><blockquote \
type="cite" class=""><div class="">Il giorno 09/gen/2015, alle ore 14:55, Johan \
Tibell &lt;<a href="mailto:johan.tibell@gmail.com" \
class="">johan.tibell@gmail.com</a>&gt; ha scritto:</div><br \
class="Apple-interchange-newline"><div class=""><div dir="ltr" class=""><div \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">Hi,</span></div><div class=""><span \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><br class=""></span></div><div class=""><font color="#404040" face="Roboto, \
arial, sans-serif" class=""><span style="line-height:18.2000007629395px" \
class="">(This was initially written as a Google+ post, but I'm reposting it here to \
raise awareness of the issue.)</span></font></div><div class=""><span \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><br class=""></span></div><span \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">The amount of CPP we have to use in Haskell is getting a bit out of \
hand.&nbsp;Here are the number of modules, per library, that use CPP for some of the \
libraries I maintain:</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><br style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">containers 18/18</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">hashable 4/5</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">unordered-containers 6/9</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">network 3/7</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">cassava 4/16</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">cabal/cabal-install 13/75</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">cabal/Cabal 7/78</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">ekg 1/15</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><br style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">If this doesn't look like a lot to you (I hope it does!) consider than some \
languages don't use CPP at all (e.g. Java).</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><br style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">CPP really sucks from a maintenance perspective:</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><br style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">&nbsp;* It's not Haskell, but this bizarre string concatenation \
language.</span><br style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">&nbsp;* The code is harder to read, bitrots more easily, and is harder to \
test.</span><br style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">&nbsp;* The code can't be compiled&nbsp;without using Cabal (which generates \
some of the CPP macros for us.) This hurts e.g. ad-hoc \
testing/benchmarking.</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><br style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">There are a couple of reasons we use CPP, but the main one is breaking \
changes in GHC and libraries we depend on. We need to reduce these kind of breakages \
in the future. Dealing with breakages and maintaining the resulting CPP-ed code is \
costing us time we could spend on other things, such as improving our libraries or \
writing new ones. I for one would like to get on with writing applications instead of \
spending time on run-of-the-mill libraries.</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><br style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">Often these breaking changes are done in the name of "making things \
cleaner". Breaking changes, no matter how well-intended, doesn't make code cleaner, \
it makes it less clean*. Users end up having to use&nbsp;</span><b \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">both</b><span \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">&nbsp;the old "unclean" API&nbsp;</span><b \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">and</b><span \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">&nbsp;the new "clean" API.</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><br style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">The right way to move to evolve an new API is to add new functions and data \
types, not modify old ones, whenever possible.</span><br \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><br style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><span style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">* It takes about 3 major GHC releases (~3 years) before you can remove the \
CPP, but since new things keep breaking all the time you always have a considerable \
amount of CPP.</span><br class=""><div class=""><span \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class=""><br class=""></span></div></div></div></blockquote><div><br \
class=""></div><div>Hi</div><div><br class=""></div><div>I'm an outsider so this \
could probably sound ingenuous but,</div><div>why not thinking about an in-language \
feature to solve the</div><div>problems addressed by CPP?</div><div><br \
class=""></div><div>I think these all fall into:</div><div>Enable some top-level \
declaration only if the XYZ feature is</div><div>available.</div><div><br \
class=""></div><div>This feature should allow the user to specify different \
definitions</div><div>of the same symbol depending on the availability of \
compiler</div><div>features but also _modules_ features.</div><div><br \
class=""></div><div>So if I can declare and export a newFunc function from my \
module&nbsp;</div><div>only if DataKinds is supported, I can do it explicitly instead \
of</div><div>relying on GHC version X.Y.Z. On the other hand, the users \
of</div><div>my module can decide if they want to compile some code \
depending</div><div>on the fact that my module exports the function or \
not.</div><div><br class=""></div><div>This should not be limited to "the module \
exports the function". Other</div><div>types of "features" could be tested over, and \
modules should be</div><div>able to declare the new features added which deserve to \
be tested</div><div>in this way. For example, if in the 2.0 version of my module \
I've</div><div>increased the laziness of my data structure, I can export the \
feature</div><div>"MyModule.myFunc is lazy" (encoded in some way). Then \
the</div><div>user can decide which implementation of its algorithm to use \
depending</div><div>on this.</div><div><br class=""></div><div>I think a system like \
this should solve the majority of maintenance burden</div><div>because:</div><div>- \
Dependencies on libraries features are explicit and the version</div><div>&nbsp; \
numbers needed to support them can be inferred by cabal.</div><div>&nbsp; For example \
cabal could support a syntax like containers(with:foo_is_lazy)</div><div>&nbsp; \
instead of containers &gt;= x.y.z</div><div>- GHC can automatically warn about \
features that are supported by all the&nbsp;</div><div>&nbsp; currently supported \
versions of GHC so that the checks can be removed.</div><div>- Code is more testable \
because the test suite could run tests multiple times,</div><div>&nbsp; each time \
"faking" the availability of certain features, with the GHC \
support&nbsp;</div><div>&nbsp; of a "fake old version mode" where it has simply to \
pretend to not know</div><div>&nbsp; the existence of a certain extension (not at all \
a "compatibility mode", to be</div><div>&nbsp; clear). As for library features, \
integrating with cabal sandboxes one could&nbsp;</div><div>&nbsp; automatically \
switch library versions to run the test with.</div><div>- Other?</div><div><br \
class=""></div><div>I repeat: I'm an outsider of the world of maintenance of the \
haskell packages,</div><div>so I could be missing something obvious. Hope this can be \
useful though.</div><div><br class=""></div><br class=""><blockquote type="cite" \
class=""><div class=""><div dir="ltr" class=""><div class=""><span \
style="color:rgb(64,64,64);font-family:Roboto,arial,sans-serif;font-size:13px;line-height:18.2000007629395px" \
class="">— Johan</span></div></div></div></blockquote><br \
class=""></div><div>Bye,</div><div>Nicola</div><br class=""></body></html>



_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic