ekmett / semigroupoids

Home Page:http://hackage.haskell.org/package/semigroupoids

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 foldX1family 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. ;)

commented

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 Semifoldables are Foldable, but not all Foldables 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.

commented

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
commented

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.

commented

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.

commented

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.

commented

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...