Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New modules PQueue and PQueue.Prio where order type is a type parameter #14

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

thielema
Copy link
Collaborator

This is finally my proposed code for solving #8.
I defined the type Wrap that is like the Down datatype
but it has a 'top' type parameter to choose
whether minimum or maximum should be at the top of the queue.
The new modules PQueue and PQueue.Prio are rather copies
of their Max counterparts, using Wrap instead of Down.
I like that approach very much! :-)
Thus I'd propose to phase out public PQueue.Min/Max and PQueue.Prio.Min/Max modules slowly,
that is, add new functions only to PQueue and PQueue.Prio,
and somewhen deprecate PQueue.Min/Max and PQueue.Prio.Min/Max,
and very far in the future remove Max modules and Down and make Min modules private.
It will save you code and Haddock duplication.
(As we have seen, it is pretty easy to get the duplicated Haddock comments wrong.)

It is based on the implementation of MaxPQueue
but uses new data type (Wrap top) instead of Down.

PQueue.Top: generalization of Down data type.

Bump version to 1.3.3
PQueue.Prio.PQ: export data constructor temporarily

It is based on the implementation of MaxQueue but uses Wrap instead of Down.
PQueue.mapMaybe: use it here
…m PQueue.Prio

This way, we do no longer need to expose the PQ constructor.
@lspitzner
Copy link
Owner

Ok I have considered this PR and its design. I have not fully understood all details but I can see how this proposal is a generalization of the existing Min/Max duplication and it matches what I had in mind after the discussion in the issue. However I have a number of reservations.

Most importantly, when I said

If this is correct and existing users won't be disturbed by such a change, I'll happily merge a PR.

I meant that users should not be disturbed. No phase-outs, no deprecation.

With this constraint, this PR in its current form increases code duplication. On a related note, the Min/Max duplication primarily is duplication of the interface, and a generalization of the interface does not remove the complexity, but merely moves the burden to the consumer of the interface. In haskell there exist a good amount of general-purpose generalizations where this makes sense, but the generalization used here seems rather purpose-specific. As a consequence, I give the code-duplication argument a rather low subjective score, which conversely means that I am not completely opposed to adding this generic interface in addition to the existing Min/Max interfaces. I also see the possibility for turning the Max interface into a completely shallow wrapper around Queue Max, even when this is probably less of an improvement than what you had in mind.

For more formal matters, and this confused me somewhat: I thought your main purpose of (re)adding the test-suite was to test this code, yet you opened this PR without adapting the suite to cover the newly introduced code in any way. Am I missing something?

Regarding the new constructs (mainly the Top module):

  • Some comments on the newly introduced constructs would be helpful. (Especially the type class - I generally dislike classes that lack any laws or explanations.)
  • The names confused me somewhat: "Top" feels semantically close to "maximum", which makes it a bad choice for a thing that can me Max or Min. "First" would be a better choice imo. And "Wrap" is very close to the Tagged type - it differs only in the Ord-instance, as far as I can tell. I think this fact deserves a comment too, if not a rename.
  • I am mildly certain that the newtypes (Compare, ToList, …) can be avoided. I won't discuss this until the more important issues are resolved.

@thielema
Copy link
Collaborator Author

thielema commented Mar 16, 2017 via email

@lspitzner
Copy link
Owner

My proposed code does not save code at the library side, and it cannot, given that the existing interface shall be preserved and that it would require at least a substantial amount of wrapper code.

But you implied that it did when you said

It will save you code and Haddock duplication.
(As we have seen, it is pretty easy to get the duplicated Haddock comments wrong.)

@thielema
Copy link
Collaborator Author

thielema commented Mar 16, 2017 via email

@lspitzner
Copy link
Owner

Ok. I guess I merely find it confusing that you make a (apparently new) code-duplication argument in response to what was said before.

@lspitzner
Copy link
Owner

It depends on whether you imagine vertical or horizontal queues. I am happy with a horizontal queue, too. Unfortunately Git does not support renaming identifiers like Darcs does.

I don't see how "first" is horizontal (?) And is Darcs vs Git relevant at all for this discussion?

@thielema
Copy link
Collaborator Author

thielema commented Mar 16, 2017 via email

@lspitzner
Copy link
Owner

Which remains irrelevant. Sorry.

@lspitzner
Copy link
Owner

Ok, back to more constructive feedback: Let me explain what I have in mind to avoid the newtypes, one sec.

@thielema
Copy link
Collaborator Author

thielema commented Mar 16, 2017 via email

@lspitzner
Copy link
Owner

I understand, and I can see that the whole process may be frustrating for you. However I still think that I have properly communicated the constraint that I would put on this PR early on.

I'll say this again to make it clear: I am not opposed to merging this on the basis that it increases duplication; I was actually arguing for duplication in my initial reply to this PR.

Am I assuming correctly that you still want this merged, even when we do not deprecate the existing interface?

@thielema
Copy link
Collaborator Author

thielema commented Mar 16, 2017 via email

@lspitzner
Copy link
Owner

I assumed that by no longer adding public functions to Min/Max you would not disturb existing users. You can make the other phases of deprecation as long as you want. :-)

Ok, fair :-) Then we are on the same page.

@lspitzner
Copy link
Owner

When defining Top First like this:

class First first where
  switch :: a -> a -> TaggedF first a

instance First Min where switch f _ = TaggedF f
instance First Max where switch _ f = TaggedF f

we can avoid the Compare newtype like this:

instance (First first, Ord a) => Ord (TaggedF first a) where
  compare x y = unTaggedF $ switch compare (flip compare) <*> x <*> y

And by defining

unSwitchQueue
  :: (Min.MinQueue (TaggedF first a) -> TaggedF first b) -> Queue first a -> b
unSwitchQueue f (Q q) = unTaggedF (f q)

we can get rid of Foldr, Foldl, ToList and write, e.g.

foldrDesc f z = unSwitchQueue $ \q ->
  switch (Min.foldrDesc (f . unTaggedF) z q) (Min.foldrAsc (f . unTaggedF) z q)

I haven't tested this either, but it should have the same semantics; it at least compiles so far. Or is there some inherent disadvantage to this approach which I miss? Using Applicative or appropriate general-purpose helpers (unswitch) seem to serve the purpose of "connecting the phantom types" just as well as the newtypes.

@lspitzner
Copy link
Owner

(unswitch may very well be a bad name; it acts together with switch in these cases but perhaps implying that it is some kind of dual is wrong.)

@lspitzner
Copy link
Owner

I would have committed the changes I made already, but they don't compile yet. I have pushed the not-yet-compiling code to my generic-top branch.

@thielema
Copy link
Collaborator Author

thielema commented Mar 16, 2017 via email

@thielema
Copy link
Collaborator Author

thielema commented Mar 16, 2017 via email

@lspitzner
Copy link
Owner

True, this still requires 2 helper functions in place of the 5 newtypes (I don't count unTaggedF as a helper function; the newtype-approach requires runFoo after all in addition to the newtype definition). I still like the newtype-free approach because there are only two "carriers" for the phantom type: TaggedF and Q, and the helper functions have the clear purpose of forcing the phantom types of these two types to line up.

@lspitzner
Copy link
Owner

Btw, can we merge the Private and Internals modules?

@thielema
Copy link
Collaborator Author

thielema commented Mar 16, 2017 via email

@thielema
Copy link
Collaborator Author

thielema commented Mar 16, 2017 via email

@lspitzner
Copy link
Owner

As I said, I can convert your implementations to the newtype style, and then there remain only 2 newtypes, one corresponding to unSwitchQueue and one corresponding to unSwitchQueueConstr.

Right. the difference is indeed fairly small.

Both this suggestion and the question of how to name things don't hold back this PR. I'd like proper testing and some comments (and I was just as guilty when not adding my reasoning for TaggedF vs Tagged in the code). The newtypes are not even user-facing, so we'd be able to switch at any point anyways. Only "top" vs "first" is user-facing, thinking of it.

@lspitzner
Copy link
Owner

Prio.Internals is the private module for Prio.Min and Prio.Private is the private module for Prio. They could have better names, but I would not merge them.

Good point. Also deserves a comment at the top of these two modules :-)

@thielema
Copy link
Collaborator Author

thielema commented Mar 16, 2017 via email

@lspitzner
Copy link
Owner

haha :-)

@thielema
Copy link
Collaborator Author

thielema commented Mar 16, 2017 via email

@lspitzner
Copy link
Owner

I agree, in general switch :: f Min -> f Max -> f top can provide stronger guarantees. But when using freshly constructed newtypes, you get no additional safety (if you switch the arguments to switch in this PR, it still compiles in a few cases that I have tested at least).

Now I wonder if one can avoid newtypes even with that.. I have to play with this a bit. (And no, newtypes are not evil and this is probably not very important.)

@treeowl
Copy link
Collaborator

treeowl commented Nov 17, 2020

I've been skimming comments, but ... I'm so far failing to understand the point of any of this. What's wrong with just turning a concrete min-priority queue into a max-priority queue using Down? It seems dead simple already.

@lspitzner lspitzner mentioned this pull request Dec 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants