13 comments

  • nanolith 2 days ago ago

    There is plenty of room for debate over this advice. Prefer do notation if the effects of the flow are more important than the data structure, and prefer using Applicative style if the data structure layout is more important to understand than the effects to build it. In the example in the article, the software is easier to understand from the perspective of how data was acquired to populate the record. However, this isn't true in a general sense; often records are purpose built in higher level languages to reflect effects and constrain them.

    Example: when using Parsec or similar, Applicative style is far easier to understand when examining how records are parsed. In this case, the records will likely reflect the AST, which in turn will reflect the grammar. The two reinforce each other: additional constraints on syntax will naturally flock to data constructors.

    • lemonwaterlime 2 days ago ago

      My view is more in line with your statement.

      > Prefer do notation if the effects of the flow are more important than the data structure, and prefer using Applicative style if the data structure layout is more important to understand than the effects to build i.

  • kccqzy 2 days ago ago

    I personally think of the distinction as whether the name is important. In Applicative style you apply the arguments to the constructor in order. In do notation you give names to the intermediate pieces. If the names are important enough to be stated (which is usually the case for records) use do notation. Otherwise don’t. This really reminds me of languages like Swift that have external names (aka argument label); whether a function parameter deserves an external name should be decided on a case-by-case basis and similarly here.

  • Garlef 2 days ago ago

    From an outsiders perspective, this kind of discussion seems... immature?

    (As in: Kids discussing something "important" to them while the parents chuckle)

    I like the do notation better because it expresses the intent at the point of usage and I don't have to look up the definition when coming back after a few weeks.

    • kccqzy 2 days ago ago

      An analogy is that in Python someone declared a function

          def foo(bar, baz, womp): 
      
      There is a discussion on whether you should call it like

          foo(1, 2, 3)
      
      Or like

          foo(bar=1, baz=2, womp=3)
      
      Coding style discussions tend to be like this.
    • throwway262515 2 days ago ago

      Immature in what way? TFA advocates a certain coding style over a different one.

  • throwaway17_17 2 days ago ago

    In the ‘Caveats’ section the author discusses that the advice only applies to types constructed via Record Syntax and that datatypes with positional arguments are exempt from the rule/preference proposed.

    I wonder if the argument for the do notation’s usage being better along several metrics can stand generalization. There are sometimes discussions about the merits of named argument to functions and data constructors versus the much more common positional syntax, the arguments made in favor of more ‘broad spectrum’ named arguments reflect the more specific pro’s being argued in TFA.

    It would be interesting to see what types of syntax additions, and the accompanying weight of those additions throughout an entire code base, would prove an acceptable trade off for moving away from positional arguments being the default. I almost assume the extra line noise and extra name selection would be the most negative effects, but the most positive effects are harder for me to guess at. But overall, the reaction of devs to more ‘book keeping’ in there code is hardly ever positive.

  • wazHFsRy 2 days ago ago

    In the authors enter first and last name example, isn’t the do notation also much clearer on order of effects? Eg being asked first for first name?

    • throwway262515 2 days ago ago

      Applicative operators effect from left to right. So the order of effects in

        getPerson :: IO Person
        getPerson =
          Person
            <$> (putStrLn "Enter your first name:" *> getLine)
            <*> (putStrLn "Enter your last name:"  *> getLine)
      
      is transparently clear (to a seasoned Haskeller).
      • kccqzy 2 days ago ago

        That’s only true when you use the IO monad as an Applicative. It’s not true if you use for example the Concurrently type from the async package (which is quite wonderful btw). The ApplicativeDo language extension makes it possible for you to use do notation in this case as well.

        https://hackage-content.haskell.org/package/async-2.2.6/docs...

        • throwway262515 2 days ago ago

          Do you have a concrete example using Concurrently where the effect semantics is harder to understand--possibly even misleading--with effect combinators than with do notation?

          • tome a day ago ago

            Here's a silly but simple example:

                newtype FlippedIO a = MkFlippedIO { runFlippedIO :: IO a }
                  deriving Functor
                
                instance Applicative FlippedIO where
                  pure = MkFlippedIO . pure
                  liftA2 f (MkFlippedIO x) (MkFlippedIO y) =
                    MkFlippedIO ((flip . liftA2 . flip) f x y)
                
                data Person = Person String String
                  deriving Show
                
                putStrLnFlipped = MkFlippedIO . putStrLn
                
                getLineFlipped = MkFlippedIO getLine
                
                getPerson :: IO Person
                getPerson = runFlippedIO $
                  Person
                    <$> (putStrLnFlipped "Enter your first name:" *> getLineFlipped)
                    <*> (putStrLnFlipped "Enter your last name:"  *> getLineFlipped)
            
            It runs things "backwards":

                ghci> getPerson
                One
                Enter your last name:
                Two
                Enter your first name:
                Person "Two" "One"
          • kccqzy 2 days ago ago

            When you use Concurrently you are implying that you want things to happen concurrently and you don’t care about effect ordering. You leave the effect ordering to the GHC runtime in how it schedules these green threads. Using do notation here works but it is slightly misleading. A seasoned Haskeller would never see do notation and assume anything about the ordering of effects.