Drastic Prelude changes imminent

S. Doaitse Swierstra doaitse at swierstra.net
Sun Feb 1 21:38:24 UTC 2015


As a humble maintainer of a few libraries I would appreciate if some guidance could be given as to how to keep my libraries working with older versions of GHC and this new Prelude.  Thus far the number of options and flags to achieve interoperability seems to grow and grow.

Besides that I understand that the new version is not yet available on my Mac, so I do not even dare to change my GHC implementation to find out what the consequences are of this new proposal. Too often I have had to throw away my complete Haskell infrastructure since I got lost in cabal hell, versions mismatch, etc, . 

 Doaitse




> On 01 Feb 2015, at 21:11 , Matthias Hörmann <mhoermann at gmail.com> wrote:
> 
>> However,  it'd be nice if we could come up with a design that, more or less, the whole community thinks is a good one.
> 
> If we only ever added things to the language that everyone could
> completely agree on we would end up with something
> as conservative as Java in terms of adding new features. Haskell
> should strive to improve while it is still obscure enough
> that we can change things like this.
> 
> On Sun, Feb 1, 2015 at 8:59 PM, Iavor Diatchki <iavor.diatchki at gmail.com> wrote:
>> Hello,
>> 
>> I think that "dead end #3" is the right choice, and keep Foldable etc. in
>> their own modules.   We have a module system, we should use it.  In general,
>> I think if we are to "burn bridges" it should be by moving things out of the
>> Prelude, not adding more abstractions there.
>> 
>> For the sake of concreteness, here are some of the things I don't like about
>> the current design:
>> 
>>  - the design of 'Foldable'.  When you ask GHCi about what are the members
>> of the class, you are presented with a long list of more or less random
>> methods.  This does not seem like a clean abstraction (e.g., why are 'sum'
>> and 'product' there??).
>> 
>>  - the 'Monoid' class.  Monoids are a useful abstraction, but encoding them
>> as a Haskell class is very much a compromise (in my mind, anyway).  Keeping
>> them in their own module seems like a good idea.
>> 
>>  - the state of Data.List:  I generally prefer that, when possible, a
>> datatype should provide  non-overloaded access to its functionality, and in
>> addition, there can be instances to expose the same functionality via an
>> overloaded API.
>> 
>> Of course, I can generally work around all of those, so not a big deal.
>> However,  it'd be nice if we could come up with a design that, more or less,
>> the whole community thinks is a good one.
>> 
>> -Iavor
>> 
>> I happen to support the vast majority of my packages for 3 major GHC
>> releases, not just platforms, and for the current stackage build as well.
>> 
>> The idea that we can't personally benefit from things for ~3 years because
>> we happen to have packages with long support cycles leads to a fallacy by
>> which by induction no progress can ever be made to benefit the larger pool
>> of Haskell users we will have in the future because we can never pay a price
>> in the short term to benefit them.
>> 
>> Regarding Foldable, there is a chain of reasoning that begins with accepting
>> the Applicative-Monad Proposal:
>> 
>> Once we accept Applicative as a superclass of Monad then mapM is _never_ the
>> right abstraction.
>> 
>> Why?
>> 
>> mapM is needlessly constrained to Monad, so you really start needing the
>> ability to traverse with Applicative.
>> 
>> mapM actively gets in the way of making code work with the reduced
>> Applicative constraints that are possible post AMP.
>> 
>> Dead End #1. We can't just generalize the signature of mapM in a way that
>> makes mapM work with Applicative without a whole host of other issues. (mapM
>> is a member of the Traversable class alongside traverse, there exists a
>> corner case where Traversable.mapM can have better termination behavior on a
>> weirdly designed container, so having Prelude mapM and Traversable.mapM
>> generalized differently would be bad.)
>> 
>> Dead End #2. To go the other way and put in a monomorphized traverse into
>> Prelude would break insane amounts of code across the entire ecosystem.
>> 
>> To expose traverse is to expose Traversable.
>> 
>> Exposing Traversable from the Prelude then really wants to drag in the
>> superclasses of Traversable lest you have a class that is exposed in the
>> Prelude with a superclass you can't define.
>> 
>> Dead End #3. We could have added Applicative as a superclass of Monad
>> without adding it to the Prelude, but it would have been a bad way to
>> proceed. Now users who want to either benefit from the change or define a
>> Monad, ever, would have to add an extra import Control.Applicative line,
>> what is the probem. This is the same argument against forcing Foldable into
>> hiding while possibly exposing Traversable, just mutatis mutandis for
>> Foldable/Applicative and Traversable/Monad.
>> 
>> Consequently, exposing the pieces you need to define classes that are
>> exposed through Prelude seems to be the direction that in the long term is
>> the most defensible in terms of how to explain the state of the world to
>> newcomers, once we get the hurdle of transition.
>> 
>> The current state of affairs was quite eloquently summarized by this
>> transcript from Nick Partridge about the process of describing the AMP to
>> his coworkers:
>> 
>> 
>> In a conversation with co-workers about these changes, I fired up a ghci
>> session to show the difference betweenmapM and traverse and what it would
>> mean to generalize the functions in Prelude, and how traverse was just a
>> more general mapM.
>> 
>> Here is how that went down:
>> 
>>> :i traverse
>> 
>> Top level:
>>    Not in scope: ‘traverse’
>>    Perhaps you meant ‘reverse’ (imported from Prelude)
>> 
>> Ah, of course. I forgot the import.
>> 
>>> import Data.Traversable
>>> :i traverse
>> class (Functor t, Data.Foldable.Foldable t) =>
>>      Traversable (t :: * -> *) where
>>  traverse ::
>>    Control.Applicative.Applicative f => (a -> f b) -> t a -> f (t b)
>>  ...
>>    -- Defined in ‘Data.Traversable’
>> 
>> Great. Now let's look at mapM:
>> 
>>> :i mapM
>> 
>> Top level:
>>    Ambiguous occurrence ‘mapM’
>>    It could refer to either ‘Data.Traversable.mapM’,
>>                             imported from ‘Data.Traversable’
>>                          or ‘Prelude.mapM’,
>>                             imported from ‘Prelude’ (and originally defined
>> in ‘Control.Monad’)
>> 
>> Oh. Okay we'll check out the one in Prelude by prefixing it.
>> 
>>> :i Prelude.mapM
>> Prelude.mapM :: Monad m => (a -> m b) -> [a] -> m [b]
>>    -- Defined in ‘Control.Monad’
>>> :t traverse
>> traverse
>>  :: (Traversable t, Control.Applicative.Applicative f) =>
>>     (a -> f b) -> t a -> f (t b)
>> 
>> Now let's specialise traverse for Monad and List, to show that it's
>> equivalent.
>> 
>>> :t (traverse :: Monad m => (a -> m b) -> [a] -> m [b])
>> 
>> <interactive>:1:2:
>>    Could not deduce (Control.Applicative.Applicative m1)
>>      arising from a use of ‘traverse’
>>    from the context (Monad m)
>>      bound by the inferred type of
>>               it :: Monad m => (a -> m b) -> [a] -> m [b]
>>      at Top level
>>    or from (Monad m1)
>>      bound by an expression type signature:
>>                 Monad m1 => (a1 -> m1 b1) -> [a1] -> m1 [b1]
>>      at <interactive>:1:2-50
>>    Possible fix:
>>      add (Control.Applicative.Applicative m1) to the context of
>>        an expression type signature:
>>          Monad m1 => (a1 -> m1 b1) -> [a1] -> m1 [b1]
>>        or the inferred type of it :: Monad m => (a -> m b) -> [a] -> m [b]
>>    In the expression:
>>      (traverse :: Monad m => (a -> m b) -> [a] -> m [b])
>> 
>> Curses! Looking forward to AMP. Insert explanation about how Applicative
>> should be a superclass of Monad. Pretend that it really is. Wave hands
>> about.
>> 
>>> :t (traverse :: Applicative m => (a -> m b) -> [a] -> m [b])
>> 
>> <interactive>:1:14:
>>    Not in scope: type constructor or class ‘Applicative’
>> 
>> Okay this is getting silly.
>> 
>>> import Control.Applicative
>>> :t (traverse :: Applicative m => (a -> m b) -> [a] -> m [b])
>> (traverse :: Applicative m => (a -> m b) -> [a] -> m [b])
>>  :: Applicative m => (a -> m b) -> [a] -> m [b]
>> 
>> This is really really common teaching/learning scenario using the current
>> state of the Prelude.
>> 
>> 
>> On Sat, Jan 31, 2015 at 12:59 PM, Johan Tibell <johan.tibell at gmail.com>
>> wrote:
>>> 
>>> I re-read the goals* of the proposal:
>>> https://wiki.haskell.org/Foldable_Traversable_In_Prelude
>>> 
>>> "One goal here is that people should be able to use methods from these
>>> modules without the need of a qualified import -- i.e. to prevent clash in
>>> the namespace, by resolving such in favor of the more generalized versions.
>>> Additionally, there are some new methods added to the Foldable class because
>>> it seems to be the "right" thing."
>>> 
>>> Before FTP, I would have to write this to use Foldable/Traversable:
>>> 
>>>    import qualified Data.Foldable as F
>>> 
>>> The import is qualified as to not collide with the Prelude.
>>> 
>>> If I have code that needs to be compatible with more than one GHC release
>>> (I typically need compatibility with the last 3 major releases), what do I
>>> have to write post-FTP? Since I cannot rely on the Prelude being generalized
>>> (because I might be compiling with a pre-FTP compiler) I need to either
>>> write:
>>> 
>>>    #if MIN_VERSION_base(x,y,z)
>>>    -- Get Foldable etc from Prelude
>>>    #else
>>>    import Data.Foldable (...)
>>>    import Prelude hiding (...)  -- the same
>>>    #endif
>>> 
>>> Which is terrible. Alternatively I can write
>>> 
>>>    import qualified Data.Foldable as F
>>> 
>>> but now nothing is gained over the pre-FTP state. Only after 3+ years (at
>>> the current GHC release phase) I can drop that one extra import. One out of
>>> perhaps 20. That seems quite a small gain given that we will then have made
>>> Data.List a very confusing module (it's essentially Data.Foldable under a
>>> different name), broken some code due to added type ambiguity, and also
>>> removed one of the simpler ways to remove that ambiguity, which would have
>>> been to import one of the monomorphic list functions that no longer exist.
>>> 
>>> * People tell me that there are other goals, but they aren't really stated
>>> clearly anywhere.
>>> 
>>> -- Johan
>>> 
>>> 
>> 
>> 
>> _______________________________________________
>> Libraries mailing list
>> Libraries at haskell.org
>> http://www.haskell.org/mailman/listinfo/libraries
>> 
>> 
>> _______________________________________________
>> Libraries mailing list
>> Libraries at haskell.org
>> http://www.haskell.org/mailman/listinfo/libraries
>> 
> _______________________________________________
> Libraries mailing list
> Libraries at haskell.org
> http://www.haskell.org/mailman/listinfo/libraries



More information about the Libraries mailing list