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.
> 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.
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.
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.
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.
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.
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?
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"
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.
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.
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.
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.
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.
An analogy is that in Python someone declared a function
There is a discussion on whether you should call it like Or like Coding style discussions tend to be like this.Immature in what way? TFA advocates a certain coding style over a different one.
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.
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?
Applicative operators effect from left to right. So the order of effects in
is transparently clear (to a seasoned Haskeller).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...
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?
Here's a silly but simple example:
It runs things "backwards":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.