-
Notifications
You must be signed in to change notification settings - Fork 83
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
Respect newlines between 'where' bindings #554
Comments
Should this also apply to |
Proposal (1) sounds more consistent to me: that way there is no difference between how we treat top-level-bindings and local bindings. Furthermore, it makes sense to allow no blank lines between definitions in
This is in keeping with #74 (comment), which is essentially about keeping decision fatigue to a minimum. |
Agreed, it could be the same. No strong preference either way from my side. (edit: I also think the same rule could apply in a
So instead of separating by blank line if any of the bindings are already separated by a blank line, it happens if any definition is multi-line? So the bindings in the following example would get separated by empty lines? Are both where
binding1 = short
binding2 =
longerSoWePutItOnASeparateLine
binding 3 =
multiline
expression I'm not sure I like this conflation of being multi-line and getting separated, but I see how it reduces the number of possible layouts. |
This sounds good, but if we look at actual results, they are not pretty. For example, here is a function from Ormolu's code base: p_unboxedSum :: BracketStyle -> ConTag -> Arity -> R () -> R ()
p_unboxedSum s tag arity m = do
let before = tag - 1
after = arity - before - 1
args = replicate before Nothing <> [Just m] <> replicate after Nothing
f (x, i) = do
let isFirst = i == 0
isLast = i == arity - 1
case x :: Maybe (R ()) of
Nothing ->
unless (isFirst || isLast) space
Just m' -> do
unless isFirst space
m'
unless isLast space
parensHash s $ sep (txt "|") f (zip args [0 ..]) If we apply the logic, we see that the definition p_unboxedSum :: BracketStyle -> ConTag -> Arity -> R () -> R ()
p_unboxedSum s tag arity m = do
let before = tag - 1
after = arity - before - 1
args = replicate before Nothing <> [Just m] <> replicate after Nothing
f (x, i) = do
let isFirst = i == 0
isLast = i == arity - 1
case x :: Maybe (R ()) of
Nothing ->
unless (isFirst || isLast) space
Just m' -> do
unless isFirst space
m'
unless isLast space
parensHash s $ sep (txt "|") f (zip args [0 ..]) I think it is pretty obvious that this is overly spaced without any improvement for readability. |
I think that if we go with something like 1, then the heuristic should be more clever. Actually 2 seems not such a bad choice. People will be able to add a bit of spacing exactly when they want it, it doesn't seem like a bad idea, or something that will make style inconsistent in any way. |
I think (2) gives too much choice. Ormolu isn't just about consistent style, it's about automating away micro choices that are a chore to deal with. Should I add more space between these two definitions but not elsewhere? Should I do so everywhere? If there are two choices, can I at least flip between them easily with a single change in the input? etc. Perhaps it would have to have more examples where newlines between bindings is really desirable. Looking again at the ticket description, perhaps a more meaningful discriminator than whether the body is multi-line, is whether there are multiple clauses and/or type signature? So e.g. the original input in @mheinzel's example would remain as-is, and so would @mrkkrp's above? One thing I want to note about (2) is bindings are not like do-blocks: bindings are sets with no particular order, whereas statements in a do-block are an ordered sequence. It might make sense to group some elements in the sequence. |
Yet another heuristic could be: is there at least one empty line (in the input source)?
|
What @aspiwack describes is what I had in mind for option 1, and probably still my preferred option. It seems like a good tradeoff, giving the user the possibility to influence layout (especially since I don't think there is a good and intuitive heuristic), while allowing only one additional binary choice ("separate everything or nothing"). This also seems in line with the goal of "Let some whitespace be programmable". Another point worth discussing: If we pick some heuristic, it will decide to separate some bindings in code that was formatted with an old existing version of ormolu, potentially introducing many new lines when upgrading. On the other hand, if we base this on the input's formatting, existing code will stay as it is, requiring users to opt into the additional newlines per Which of these is more desirable? |
I considered 1 in two flavors from the very beginning: one where we insert newlines when we have at least one multiline definition and another one when we have at least one blank line in the original input. I'm unconvinced by this all or nothing approach though. There could be a bunch of quick one-liners and then one or more longer definitions which should be separated. If we're to come up with a heuristic, I'd just go for separation on per-definition basis. That is, short ones should be lumped together and longer ones should have blank lines around them. |
An update. I think now that it is not a good idea to rely on number of lines, because after re-formatting it may change and so there may be problems with idempotence. I checked what we do for type class instances and we just preserve user's choice, i.e. like with expressions in If we want to be consistent, that's the way to go. |
We had a lengthy discussion about this and we can't decide about the best logic for this. I'm going to delay implementation of this feature, which seems much more minor than e.g. blank lines in do-blocks. |
I don't think this is obvious, that is exactly how we style our code internally. |
I think "If there is one empty line, then all definitions are spaced out by one line. If there is no empty line, then keep it as such." is a fine stop-gap solution and we can seamlessly transition from it to "respect all newlines in |
@neongreen You've just repeated the option we discussed and rejected without adding any new argumentation.
This is neither "definite" nor obvious. If it were, the change would have been merged. |
I do think it's definite and obvious. The fact that I can't justify it in writing doesn't mean it's a useless datapoint — I think it's not. (Of course, I also don't think you /have/ to use it, though.) |
<cranky> But if you want an argument, here you go:
I don't think it's As Good As It Gets, just a fine stop-gap solution.
Yes, so we agree it's not perfect. I claim that "remove all newlines, always" has even more drawbacks. Right now "remove all newlines, always" already serves as a stop-gap solution, and I think that "either remove all newlines or add all newlines" is strictly better because it permits the current behavior while also allowing a different, often more desirable one. It also doesn't cause any formatting changes for people already using Ormolu.
Later you said you don't want that anymore, so I dismissed that part. (I also don't think it's a good idea.) |
My claim in a nutshell is that "either no newlines or all newlines, with the choice being left to the user" is strictly better than "always no newlines", and I don't see this claim being discussed and rejected earlier. If it was, I missed it. The claim that "either no newlines or all newlines IS THE BEST SOLUTION PERIOD" is stronger, I never made it, and I will not defend it. </cranky> |
Just chiming it that having no control over grouping is a major blocker for me. From my POV, it's simple: just respect the input empty lines in I don't agree at all with the "micro-decision" argument: strategically placed newlines improve readability dramatically. Code without newlines or newlines everywhere is absolutely undecipherable to me. |
I'm curious why you decided against treating where/let blocks like we already treat do blocks. It seems like the consistent thing to do. |
There are very good arguments on both sides, but one thing that shouldn't be a deciding factor for a decision is a (IMO) false sense of consistency. The elements in |
After thinking about this again today, I think we should preserve blank lines found in original input between definitions in
|
Thanks for merging the PR to preserve the grouping within
do
-blocks! The main thing of #74 that I'm missing now is about newlines betweenwhere
bindings.Is your feature request related to a problem? Please describe.
When defining multiple helper functions in a
where
clause, Ormolu will turn them into a single block that for me is hard to parse into individual bindings. Others seem to have similar issues and in some cases even added empty comments to visually separate the bindings.Describe the solution you'd like
There where two approaches already described in #74, I'm fine with either one:
Newline between all bindings or none, depending on existing newlines (similar to type signatures)
Note that this should always remove newlines between multiple clauses of the same definition, as it does for top-level definitions.
Individually allow single or no newline between bindings, depending on existing newlines (similar to
do
-blocks as)Describe alternatives you've considered
See above.
Additional context
An example for the two suggestions:
currently gets turned into
with proposal 1 will be turned into
and with proposal 2 will stay as it was.
The text was updated successfully, but these errors were encountered: