Prefer do notation over Applicative operators when assembling records (2024)

54 points
1/21/1970
5 days ago
by wazHFsRy

Comments


nanolith

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.

3 days ago

lemonwaterlime

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.

3 days ago

kccqzy

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.

3 days ago

Garlef

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.

3 days ago

kccqzy

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.
3 days ago

throwway262515

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

3 days ago

throwaway17_17

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.

3 days ago

jdougan

Smalltalk keyword message syntax seems to be in a middle ground

4 hours ago

wazHFsRy

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?

3 days ago

throwway262515

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).
3 days ago

kccqzy

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...

3 days ago

throwway262515

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?

3 days ago

tome

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"
2 days ago

kccqzy

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.

3 days ago