hindent icon indicating copy to clipboard operation
hindent copied to clipboard

Support custom formatting for <$> <*> combinations

Open drewr opened this issue 7 years ago • 10 comments

In this example with hindent 5.1.0, my Opts builder gets auto-formatted into something that IMO is more confusing:

opts :: Options.Parser Opts
opts =
    Opts <$> argument str (metavar "MP3") <*>
    strOption
        (long "bucket" <> metavar "BUCKET" <> help "What bucket?" <> value defaultBucket) <*>
    strOption
        (long "imagePath" <> metavar "IMAGE" <>
         help "What image to attach to the podcast" <>
         value defaultImage) <*>
    option
        auto
        (long "audioScale" <> metavar "IMAGE" <>
         help "What image to attach to the podcast" <>
         value defaultScale) <*>
    strOption
        (long "albumAuthor" <> metavar "ALBUM AUTHOR" <>
         help "What image to attach to the podcast" <>
         value defaultAuthor) <*>
    (optional $
     strOption
         (long "setDate" <> metavar "YYYY-MM-DD" <> help "Date prefix override"))

drewr avatar Aug 29 '16 17:08 drewr

I've gotten used to the more regular way of formatting operators at the end of the line, but I'm fine with adding specific support for these operators to be formatted something like this:

opts :: Options.Parser Opts
opts =
  Opts <$> argument str (metavar "MP3")
       <*> strOption
           ( long "bucket"
           <> metavar "BUCKET"
           <> help "What bucket?"
           <> value defaultBucket )
       <*> strOption
           ( long "imagePath"
           <> metavar "IMAGE"
           <> help "What image to attach to the podcast"
           <> value defaultImage )
       <*> option auto
           ( long "audioScale"
           <> metavar "IMAGE"
           <> help "What image to attach to the podcast"
           <> value defaultScale )
       <*> strOption
           ( long "albumAuthor"
           <> metavar "ALBUM AUTHOR"
           <> help "What image to attach to the podcast"
           <> value defaultAuthor )
       <*> ( optional $ strOption
             ( long "setDate"
             <> metavar "YYYY-MM-DD"
             <> help "Date prefix override" ) )

chrisdone avatar Aug 30 '16 07:08 chrisdone

As <$> and <*> are essentially another form of function application, I think it makes intuitive sense to format it as suggested. If you remove those operators, you should get the same formatting, with just a little less horizontal movement.

ocharles avatar Aug 30 '16 09:08 ocharles

Good point regarding the horizontal movement. The suggested above one causes dependency on the length of the name Opts which isn't ideal. Can anyone suggest an alternative layout?

Perhaps:

opts :: Options.Parser Opts
opts =
  Opts 
    <$> argument str (metavar "MP3")
    <*> strOption
        ( long "bucket"
        <> metavar "BUCKET"
        <> help "What bucket?"
        <> value defaultBucket )
    <*> strOption
        ( long "imagePath"
        <> metavar "IMAGE"
        <> help "What image to attach to the podcast"
        <> value defaultImage )
    <*> option auto
        ( long "audioScale"
        <> metavar "IMAGE"
        <> help "What image to attach to the podcast"
        <> value defaultScale )
    <*> strOption
        ( long "albumAuthor"
        <> metavar "ALBUM AUTHOR"
        <> help "What image to attach to the podcast"
        <> value defaultAuthor )
    <*> ( optional $ strOption
          ( long "setDate"
          <> metavar "YYYY-MM-DD"
          <> help "Date prefix override" ) )

chrisdone avatar Aug 30 '16 09:08 chrisdone

This looks great @chrisdone! :dancer:

drewr avatar Aug 30 '16 14:08 drewr

I run into this problem with (&). Example input:

instance BinaryBit.BinaryBit Int32 where
    getBits _ = do
        bytes <- BinaryBit.getByteString 4
        bytes
            & LazyBytes.fromStrict
            & Endian.reverseBitsInLazyBytes
            & Binary.runGet Binary.get
            & pure

And hindent's output:

instance BinaryBit.BinaryBit Int32 where
    getBits _ = do
        bytes <- BinaryBit.getByteString 4
        bytes & LazyBytes.fromStrict & Endian.reverseBitsInLazyBytes &
            Binary.runGet Binary.get &
            pure

It feels gross to add a bunch of special cases for certain operators. I don't have any better ideas, though.

Edited to add: Mostly because you could replace & with $ or . or >>> or <<<. And that's just from the base package. You could be using |> or <| instead.

tfausak avatar Aug 30 '16 15:08 tfausak

Another solution is to always break before the operator on overlong lines. This avoids having to special case some set of operators.

Is the decision to break after operators arbitrary or is there a reason?

myProduct = 
  somethingLong
  + somethingElse
  + anotherThing

myApplicative = 
  Opts
  <$> argument str (metavar "MP3")
  <*> strOptions some more stuff

expipiplus1 avatar Dec 05 '16 14:12 expipiplus1

I like that approach. I prefer some indentation of the operators since it's required with do notation.

How would it work with parentheses? Using Chris's example from above, I see a few options:

<*> strOption (long "bucket" 
  <> metavar "BUCKET" 
  <> help "What bucket?" 
  <> value defaultBucket)

<*> strOption (
  long "bucket" 
    <> metavar "BUCKET" 
    <> help "What bucket?" 
    <> value defaultBucket)

<*> strOption 
  ( long "bucket" 
  <> metavar "BUCKET" 
  <> help "What bucket?" 
  <> value defaultBucket
  )

tfausak avatar Dec 05 '16 14:12 tfausak

My preference is for the third option

Opts
  <$> strOption 
    ( long "bucket" 
    <> metavar "BUCKET" 
    <> help "What bucket?" 
    <> value defaultBucket
    )

expipiplus1 avatar Dec 05 '16 14:12 expipiplus1

+1. I'm fine with special-casing these by default, but having to put them in the config is fine too.

NorfairKing avatar Mar 22 '17 00:03 NorfairKing

I still get output from hindent that looks like this:

arbitraryXPrvKey :: Network -> Gen XPrvKey
arbitraryXPrvKey net =
    XPrvKey <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitraryHash256 <*>
    arbitrary <*>
    pure net

I could live with it if it looked like this:

arbitraryXPrvKey :: Network -> Gen XPrvKey
arbitraryXPrvKey net =
    XPrvKey
        <$> arbitrary
        <*> arbitrary
        <*> arbitrary
        <*> arbitraryHash256
        <*> arbitrary
        <*> pure net

jprupp avatar Aug 31 '18 09:08 jprupp