Bikeshed all the things
ekmett opened this issue · comments
When we're going to do first digit bump on this package it may be worth it to finally bite the bullet and rename a bunch of things to make them more accessible:
Bind -> Semimonad
Apply -> Semiapplicative
etc.
This opens up access to standard literature on the topic.
Might I suggest:
Current name | Proposed name | |
---|---|---|
Foldable1 |
-> | Semifoldable |
Traversable1 |
-> | Semitraversable |
Bifoldable1 |
-> | Semibifoldable |
Bitraversable1 |
-> | Semibitraversable |
I also think this would be a good idea.
Only about 110 immediate downstream dependencies to change, how bad can it be. ;)
I probably won't have time to get around to this for the next couple of months, but I'm resolved to actually rip the bandaid off and do this.
@ekmett I'm really happy to hear that. Sensible naming is important and I think the Semifoldable
/Semitraverable
type class is an important part of the extended ecosystem. Ideally I'd like to see it roped into base
eventually and have the foldX1
family of functions moved from Foldable
to Semifoldable
.
There's a discussion on the libraries mailing list about moving Foldable1
into base
. However, for such a thing to be done, this need to happen first. @ekmett Although you won't have time to do this for a while, would you be alright with other people chipping in to migrate your packages that use semigroupoids
? A coworker and I both have a keen interest in getting this done, and we could PR all the changes that need to happen to your other packages.
Also, I'm assuming that we are going with the naming convention suggested in recursion-ninja's comment.
cc @chessai
@ekmett I know you're very busy. I would be willing to put in the work to do this, not just for semigroupoids, but for downstream dependencies as well.
Current name | Proposed name | |
---|---|---|
Bind | -> | Semimonad |
Apply | -> | Semiapplicative |
Foldable1 | -> | Semifoldable |
Traversable1 | -> | Semitraversable |
Bifoldable1 | -> | Semibifoldable |
Bitraversable | -> | Semibitraversable |
BindTrans | -> | SemimonadTrans |
fold1 | -> | semifold |
foldMap1 | -> | semifoldMap |
traverse1 | -> | semitraverse |
sequence1 | -> | semisequence |
bifold1 | -> | semibifold |
bifoldMap1 | -> | semibifoldMap |
bitraverse1 | -> | semibitraverse |
bisequence1 | -> | semibisequence |
intercalate1 | -> | semiintercalate |
intercalateMap1 | -> | semiintercalateMap |
traverse1_ | -> | semitraverse_ |
for1_ | -> | semifor_ |
sequenceA1_ | -> | semisequenceA_ |
foldMapDefault1 | -> | semifoldMapDefault |
asum1 | -> | semiasum |
foldrM1 | -> | semifoldrM |
foldlM1 | -> | semifoldlM |
semiasum reads horribly. A lot of the others read pretty well, although semiintercalate is kind of gross. are is To be honest, I had not considered renaming the functions. We have foldl1 and foldr1 as precedent in base. Suffixing with 1 has the advantage of precedent, plus it’s shorter. But semifold does sound pretty cool.
all the functions that aren't actual typeclass methods could possibly just keep the '1' suffix
i'm struggling against consistency here, because some functions sound great with 'semi-' as their prefix, and some sound bad
There is also a some1
on Data.List.NonEmpty#NonEmpty
to consider.
Only about 110 immediate downstream dependencies to change, how bad can it be. ;)
Within the Kmettosphere, would the intention be to support only the new "Semi" flavoured version, or to use CPP to support both major versions?
Only about 110 immediate downstream dependencies to change, how bad can it be. ;)
Within the Kmettosphere, would the intention be to support only the new "Semi" flavoured version, or to use CPP to support both major versions?
hold my beer
https://github.com/chessai/v6/tree/semigroupoids-breaking-bump
Above are all packages that @ekmett maintains (that depend directly on semigroupoids) updated to reflect the above naming changes
i might be able to do all non- @ekmett packages tomorrow.
i don't think we need CPP because 'semigroupoids' builds all the way back to ghc702, we're only changing names - so if the 95 dependencies (108total - 13 broken) that depend directly on semigroupoids fix the names, then we're fine (I think). Some of those packages are unmaintained as well.
So we just need to pick names and stick to them.
Also, in my PR on Phabricator I added some methods, which I think should also be discussed. They have already been discussed someone in #49 i believe.
they are:
-- Note that these are all typeclass methods of Semifoldable
-- that are being proposed, and not auxiliary functions
head :: Semifoldable t => t a -> a
head = getFirst . semifoldMap First
last :: Semifoldable t => t a -> a
last = getLast . semifoldMap Last
maximum :: (Semifoldable t, Ord a) => t a -> a
maximum = getMax . semifoldMap Max
minimum :: (Semifoldable t, Ord a) => t a -> a
minimum = getMIn . semifoldMap Min
They're relatively uncontroversial (?), aside from naming conflicts with stuff in base.
there's also this function, which could be useful with base >= 4.12:
foo :: (Applicative f, Semifoldable t, Semigroup a) => t (f a) -> f a
foo = getAp . semifoldMap Ap
i don't think we need CPP because 'semigroupoids' builds all the way back to ghc702
The fact that semigroupoids
builds back to the stone age so users don't need to maintain compatibility across the divide as long as we keep this package with a ridiculously long maintenance window is the real reason why I'm willing to even contemplate the change.
I'm definitely on board with all of the class renaming mentioned, along with Extend
-> Semicomonad
.
Not sure yet about the combinators, yet.
One benefit to switching them to semiwhatever
is that they then don't collide with the analogous combinators in Foldable making for an easy upgrade path and an API that can largely be used unqualified.
One downside is half the names are awful, and they are all longer than the originals.
I kind of like the suggestion made of using semifoldMap
, etc. at least for the class method names and perhaps leaving the rest unmolested.
We can proceed on this, but if you rush ahead, you may be setting up for a lot of rework. This is going to be an API change that affects a lot of people, and so I want to make sure we get it right. There is also the discussion of expanding the API of Semifoldable
, which I'd want to (semi)fold into any major version bump, if we decide to go ahead with that plan.
In my experience users tolerate API changes that improve things, but they don't tolerate thrashing. If that means breaking those internal PRs a couple of times, I think it's a small price to pay to ensure that the end users see a consistent state. Rushing ahead here means, for those purposes, we aren't the "users" and some thrashing may ensue until we get the API right and ready for release.
So let's do this in a couple of phases. We'll set up a branch with the new API and a first digit major version bump. We'll start the API rewrite there. Once we're happy with that, and it incorporates whatever API expansion we want to do, then we can start in on the PR side, but not pull the trigger on merging them until we are confident we have the API we are going to ship. I don't want to set up downstream users for rework.
I suspect I will have basically no bandwidth to consider the API changes critically until the end of June, though and I realize this is at odds with the great sense of urgency you seem to feel. ;)
Am I the only one who doesn't like the Semi-
prefix for Foldable
and Traversable
? I think it makes sense for things Monad
, because all monads are also semimonads, just like all monoids are also semigroups.
But for Foldable
and Traversable
, it's the other way around. All Semifoldable
s are Foldable
, but not all Foldable
s are Semifoldable
. Maybe the Semi-
prefix is trying to convey "fold with semigroup", but that still doesn't mean the same thing that the Semi-
in the other classes mean. I think Foldable1
is actually a better name, and shorter too.
That's a very good point. I had not considered that, but now that you bring it up, that does seem a little off. I'm still not a huge fan of Foldable1
since in base
and elsewhere in the ecosystem (hashable
,aeson
), suffixing a typeclass Foo
with the number 1 means something like this (using syntax available with QuantifiedConstraints
):
Foo1 f === forall a. Foo a => Foo (f a)
I don't know what the appropriate term for Foldable1
/Traversable1
is though.
I know about the other convention (for Eq1
and Ord1
, etc.), but I don't think it clashes with that because Eq
and Ord
are * -> Constraint
, while Foldable
is (* -> *) -> Constraint
.
But let's consider, hypothetically, an Eq1
style class for Foldable
. It would have the kind ((* -> *) -> * -> *) -> Constraint
. I'd argue that we wouldn't call that Foldable1
, but FoldableTrans
(by analogy with MonadTrans
). I don't think it's likely that people would mistake Foldable1
for such a class.
If we are apprehensive about prepending Semi
to the (Bi
)Foldable
/Traversable
typleclass names, perhaps we can append a suffix that is more expressive than 1
that denotes the non-empty nature of the containers.
Some potential suffixes I'm not too attached to:
NonEmpty
NonNull
Full
Inhabited
Total
We could do that, but I think 1
is a better suffix than any of them. If Foldable
is morally toList
, then Foldable1
is morally toNonEmpty
, i.e., a container with at least 1 element. I suppose you could make an argument for FoldableNonEmpty
, but it's too long, and FoldableNE
looks ugly to me.
We could conceive of a Foldable
class for containers that have at least 2 elements. All of the names above could equally apply to that. But Foldable2
is clearly the only good name for such a class.
The Foldable1
naming convention leaves room for such a class. Not that I necessarily have a use for such a class, but if somewhere down the line somebody does, then that room is there, which I think makes it a more robust choice of name. Or even further down the line, let's say the dependent types stuff get better, we could even conceive of a FoldableN n
which is morally toVec
or something like that (or whatever dependent types people call a container that has at least n
elements (Vec n
has exactly n
)).
I'm not convinced that there will ever be a use for Foldable2
and both Foldable1
& Foldable2
clash with the (Eq1
, Eq2
, Ord1
, Ord2
, etc) family of typeclasses' naming convention.
What about FoldableSome
, stealing the naming a bit from the many
& some
functions. I think that might be better than the other suffixes I proposed.
I also think that there is something to be said for being just a bit more verbose than one extra character in the typeclass names. It's pretty easy to miss the appended 1
and confuse a Foldable
constraint with a Foldable1
constraint. It's happened to me many times.
The 1 is still in conflict, but requires more Eq1, etc like classes to define. e.g. Monoid1 for taking a monoid to a monoid, then Foldable1 consistently in that nomenclature would let you reduce through a Monoid1.
I don’t have a particular problem with the Semi prefix on Foldable and Traversable. Intuitively, Monoid/Applicative give you tools for construction. Foldable/Traversable give you tools for consumption using those tools. The variance is flipped so the inheritance relation also flips.
When I faced the same naming conundrum for rank-2 equivalents of Functor, Applicative, Foldable, Traversable, and Distributive, I resolved it by leaning on the module system. An equivalent solution here would be exporting a top-level module Semi
, designed to be imported qualified. After the one-line import, users would get access to Semi.Foldable, Semi.foldMap, etc.
I've generally been happy with my solution. The only doubt I have is about the symbolic combinators, because I don't like seeing them prefixed. That's probably a matter of taste, and it's not a problem here anyway. The Semi.Applicative and Semi.Monad combinators would be available in their unprefixed form from Prelude, right?
The haddocks for Semi.Monad
being a superclass of Monad
someday in the far flung future would be frankly pretty awful to read, and would ensure that they could never move into Prelude
, forever dooming them to a second class existence.
In your case with the rank-2 variants, there can be no meaningful inheritance relationship, without way way more type shenanigans, so this concern doesn't arise. Here, basically every Semifoo is related to a corresponding Foo via a superclass of subclassing relationship, even if 75% of those are things we'll have to punt until the community clamors for them.
I just don't see a way to come up with slightly different names for all these classes and functions while keeping even 70% of users happy. The bikeshed is not the right metaphor here, the problem is more that is no correct colour for the bikeshed because the bikeshed shouldn't even exist.
The ideal solution might be something like
class Foldable f where
type SemiGroupConstraint :: * -> Constraint
type instance SemiGroupConstraint = Monoid
foldMap :: SemiGroupConstraint s => f a -> s
instance Foldable [] where
foldMap [] = mempty
foldMap (x:xs) = x <> xs
instance Foldable (,) a where
type SemiGroupConstraint _ = Semigroup
foldMap = snd
If there is any chance of a future GHC making this approach workable, it might be worth waiting a few years.
I tried that approach in lens for a while for all sorts of stuff in Control.Exception.Lens
. The type you sketch almost works, until you go to abstract over it. It is more than a few years away though, because you really need quantified constraints to make it viable to work with abstractly.
Renaming your API here:
class ConstrainedFoldable t where
type FoldableConstraint t :: * -> Constraint
foldMap :: FoldableConstraint m => (a -> m) -> f a -> m
with QuantifiedConstraints
this starts to become viable.
type Foldable t = (ConstrainedFoldable t, forall m. Monoid m => FoldableConstraint t m)
type Semifoldable t = (ConstrainedFoldable t, forall m. Semigroup m => FoldableConstraint t m)
Without the extra QuantifiedConstraint
there on Foldable
then this is actually strictly weaker than Foldable
today, as you need to know the instance or pointwise properties about the actual constraint to work generically with a foldable container and a monoid. But at the very least, using the API above would require Simon to finish getting the quantified constraints code up to snuff. We're not yet there with a released or unreleased GHC.
I'm inclined to fix this package now, then in 6-7 years when that is a viable alternative with a long enough support window to then start pushing in that direction. In the meantime we can definitely debate about whether we should let this block any progress on the base
front with all due vigor. I'm not sure which way I'd lean in the end. For me it'd depend on how well QuantifiedConstraints
works once its 'production ready'.
That said, moving to something like this proposal for Prelude
is a long way off. Nothing else in the Prelude relies on anything like that level of sophistication and it would be effectively be condemning initiatives like having a common haskell standard for, say, Haskell2020, to death.
I'm inclined to fix this package now, then in 6-7 years when that is a viable alternative with a long enough support window to then start pushing in that direction. In the meantime we can definitely debate about whether we should let this block any progress on the base front with all due vigor.
Of course. Just to clarify, my reply was all about the far flung future and how it should all end up looking in base
. Name mangling is an acceptable solution in semigroupoids
. Users who need the functionality have to sign up for it anyway, and must be willing to accept some amount of maintenance burden.
Given how conservative we tend to be with Prelude
in particular in base and the fact that this would be a Prelude
facing change eventually, it is indeed pretty far flung. I look forward to the day when as they cart me off to the some retirement home, we can finally push that commit into base. ;)
One drawback to the implementation that unifies Foldable
and Semifoldable
is that you lose the ability to have overridden implementations of foldr
, foldl'
, etc.
Noting that QuantifiedConstraints is no longer far flung future...