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

Formatting of long seq (and other backticked?) chains #1151

Open
endgame opened this issue Dec 26, 2024 · 4 comments
Open

Formatting of long seq (and other backticked?) chains #1151

endgame opened this issue Dec 26, 2024 · 4 comments
Labels
question Further information is requested style Nitpicking and things related to purely visual aspect for formatting.

Comments

@endgame
Copy link

endgame commented Dec 26, 2024

Describe the bug

Recent versions of ormolu (at least 0.7.2.0, which is what my nixpkgs snapshot currently provides) seem to have changed the formatting on large chains of seq calls. Can you please confirm that this layout change is intentional and stable before I re-format >330 amazonka-* packages?

To Reproduce

Have ormolu reformat the following code:

instance Prelude.NFData ListMultipartUploads where
  rnf ListMultipartUploads' {..} =
    Prelude.rnf delimiter
      `Prelude.seq` Prelude.rnf encodingType
      `Prelude.seq` Prelude.rnf expectedBucketOwner
      `Prelude.seq` Prelude.rnf keyMarker
      `Prelude.seq` Prelude.rnf maxUploads
      `Prelude.seq` Prelude.rnf prefix
      `Prelude.seq` Prelude.rnf uploadIdMarker
      `Prelude.seq` Prelude.rnf bucket

Expected behavior

I expected this code to remain unchanged, but ormolu has decided that what it really needs is a great pyramid of seq calls. Note that the adjacent Hashable instance is unchanged:

instance Prelude.Hashable ListMultipartUploads where
  hashWithSalt _salt ListMultipartUploads' {..} =
    _salt
      `Prelude.hashWithSalt` delimiter
      `Prelude.hashWithSalt` encodingType
      `Prelude.hashWithSalt` expectedBucketOwner
      `Prelude.hashWithSalt` keyMarker
      `Prelude.hashWithSalt` maxUploads
      `Prelude.hashWithSalt` prefix
      `Prelude.hashWithSalt` uploadIdMarker
      `Prelude.hashWithSalt` bucket

instance Prelude.NFData ListMultipartUploads where
  rnf ListMultipartUploads' {..} =
    Prelude.rnf delimiter `Prelude.seq`
      Prelude.rnf encodingType `Prelude.seq`
        Prelude.rnf expectedBucketOwner `Prelude.seq`
          Prelude.rnf keyMarker `Prelude.seq`
            Prelude.rnf maxUploads `Prelude.seq`
              Prelude.rnf prefix `Prelude.seq`
                Prelude.rnf uploadIdMarker `Prelude.seq`
                  Prelude.rnf bucket

Additional context

  • This code is from the autogenerated amazonka-s3 service binding. Pre-generating Hashable and NFData instances shortens compile times vs. doing it via Generics
  • That Prelude import is an import of Amazonka.Prelude, which re-exports many common functions used in service bindings need. I have configured .ormolu with module Amazonka.Prelude exports Prelude so it should pick up seq's fixity.
@amesgen amesgen added the question Further information is requested label Dec 27, 2024
@amesgen
Copy link
Member

amesgen commented Dec 27, 2024

Thanks for opening this issue! This is a consequence of the special handling of infixr 0 operators, most importantly $ and $! (also see #645):

-- | Indicate if an operator has @'InfixR' 0@ fixity. We special-case this
-- class of operators because they often have, like ('$'), a specific
-- “separator” use-case, and we sometimes format them differently than other
-- operators.
isHardSplitterOp :: FixityApproximation -> Bool
isHardSplitterOp = (== FixityApproximation (Just InfixR) 0 0)

Now seq also is infixr 0, so it gets the same treatment.

I don't have a strong opinion on that; we could add a heuristic to detect $-like operators by looking at the operator name and only format those specially (such that seq would not exhibit this behavior), but it would be yet another (potentially brittle/surprising) heuristic, so it needs to be weighed against the status quo.

@endgame
Copy link
Author

endgame commented Dec 27, 2024

I can see the rationale. How often is there a named function that wants special separator-style layout like $? Would applying your special-case rule only to operators (not named functions) declared infixr 0 be a clean enough heuristic?

@amesgen
Copy link
Member

amesgen commented Dec 27, 2024

Thoughts @mrkkrp?

@mrkkrp
Copy link
Member

mrkkrp commented Jan 6, 2025

In my opinion the situation is not just about deciding which operator gets which treatment, it is even trickier. One could imagine a legitimate case where seq is actually used exactly like ($), e.g. with a multi-line code block on its right-hand side. In that case the current behavior would actually be desirable. Maybe that's why it was made infixr 0?

This looks natural:

foo x = bar x `seq`
  baz x + quux x

I feel like if we want to have a good "common sense" solution we need to be able to apply 2 different formatting schemes for infixr 0 operators (be it a "true" operator or a backticked one). For example:

  • If we have a chain of operators all of which are infixr 0 and none of the terms are multiline, use the same logic as for normal formatting of operator chains.
  • Otherwise apply the current "hard-splitter" logic.

I am not sure how easy this is to implement though. I think we are currently span-agnostic in the code that does operator association.

@mrkkrp mrkkrp added the style Nitpicking and things related to purely visual aspect for formatting. label Jan 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested style Nitpicking and things related to purely visual aspect for formatting.
Projects
None yet
Development

No branches or pull requests

3 participants