๐Ÿ“Œ This thread has been marked cozy by the moderators. Keep it wholesome nya~ โœจ

do-notation appreciation thread โœจ

Haskell do-notation monads cozy-thread  Posted in: Functional Programming General  ยท  14 replies  ยท  1,847 views
#1
๐Ÿฏ TypeclassTigers Monadic Purrfectionist โœฆ Senior Posts: 2,341 Joined: Jan 2021 Online

okay so i've been meaning to make this thread for a while and today is finally the day!! i want everyone to come in here and share something they genuinely love about do-notation ๐Ÿ’–

i'll start! the thing i keep coming back to is how do-notation is just beautiful syntactic sugar over monadic bind, and the desugaring is so elegant. like this simple Maybe example:

-- with do-notation (so readable!!) safeDiv :: Int -> Int -> Maybe Int safeDiv x y = do guard (y /= 0) return (x `div` y) -- desugared (what GHC actually sees ~nya~) safeDiv' :: Int -> Int -> Maybe Int safeDiv' x y = guard (y /= 0) >> return (x `div` y)

like... the do version reads like imperative code but it's secretly completely pure and referentially transparent ๐ŸŒธ every single time i explain this to someone new to Haskell and their eyes light up it makes me so happy

what do you girlies love about do-notation?? nya~ ๐Ÿฑโœจ

/\ /\ ( o o ) =( Y )= TypeclassTigers ) ( "the typeclass is the territory" (_)-(_)
๐Ÿ’– 12 โœจ 8 ๐Ÿฑ 5
#2
๐ŸŒ™ LambdaLunaCat ฮฒ-reducer โœฆ Member Posts: 788 Joined: Sep 2022 โ— Offline

YESSS this is the thread i needed today ๐Ÿ’–๐Ÿ’–

okay my favorite thing is the full desugaring rules!!! they are so clean. like the general case for any monad:

-- do { x <- m; rest } desugars to: m >>= (\x -> do { rest }) -- do { m; rest } desugars to: m >> (do { rest }) -- do { let x = e; rest } desugars to: let x = e in do { rest } -- do { e } desugars to: e

it's just lambda calculus all the way down ๐Ÿ˜ญ๐Ÿ’• the <- arrow in do-notation is literally just binding a variable in a lambda. it looks like variable assignment from imperative world but it's not mutating anything!!! it's just a continuation!!!

this is one of those things that once it clicks in your brain you can never unsee it nya~~ ๐Ÿฑ

~ LambdaLunaCat ~ ฮปx. x โ‰ก id โ‰ก ๐Ÿฑ
๐Ÿ’– 9 โœจ 11 ๐Ÿง  7
#3
๐Ÿ”ฎ FunctorFelicia Applicative Enjoyer โ—ˆ Mod Posts: 5,102 Joined: Jul 2019 Online

THREAD OF THE YEAR. i'm pinning this ๐Ÿ“Œ

I want to talk about ApplicativeDo because it doesn't get enough love!!!

So normally do-notation requires a full Monad instance. But with {-# LANGUAGE ApplicativeDo #-}, GHC will desugar your do-block using <$> and <*> wherever possible, only falling back to monadic bind when it actually needs it:

{-# LANGUAGE ApplicativeDo #-} -- These two binds don't depend on each other! -- With ApplicativeDo, GHC can run them in parallel ๐ŸŒธ fetchUserAndPost :: Fetch (User, Post) fetchUserAndPost = do user <- fetchUser 42 post <- fetchPost 99 -- independent of `user`! return (user, post) -- GHC desugars this to approximately: -- (,) <$> fetchUser 42 <*> fetchPost 99

ApplicativeDo enables an alternative translation for do-notation which uses <$>, <*>, and join as far as possible โ€” meaning you can use do-notation with types that are only Applicative and Functor, not full Monad!

ApplicativeDo desugaring preserves the original semantics, provided that the Applicative instance satisfies <*> = ap and pure = return โ€” so you can normally turn on ApplicativeDo without fear of breaking your program. ๐Ÿ’–

the fact that you can write readable sequential-looking code and have the compiler figure out what can be parallelized is just... *chef's kiss* ๐Ÿฑโœจ

โœฆ FunctorFelicia โœฆ [ CGPA Mod ] fmap purr catGirl = catGirl { sound = "purr" }
๐Ÿ’– 14 โœจ 10 ๐Ÿ”ฎ 6 ๐Ÿฑ 8
#4
๐Ÿˆ KleisliKitten arrow enthusiast โœฆ Member Posts: 423 Joined: Mar 2023 โ— Offline

can i share my favorite do-notation moment ever?? it was when i realized that the list monad makes do-notation work like list comprehensions ๐Ÿ˜ญ๐Ÿ’•

-- This do-block on lists... pairs :: [(Int, Int)] pairs = do x <- [1..5] y <- [x..5] guard (x + y == 6) return (x, y) -- ...is EXACTLY the same as this comprehension! pairs' = [(x, y) | x <- [1..5], y <- [x..5], x + y == 6] -- result: [(1,5),(2,4),(3,3)] โœจ

ONE abstraction. works for Maybe (handling failure), for IO (sequencing effects), for lists (nondeterminism), for parsers, for State, for Reader... it's the same >>= every time!!! just a different monad!!! nya~~~

this is why i became a Haskell girl tbh. the unification is just *so beautiful* ๐ŸŒธ

|\ _,,,---,,_ /,`.-'`' -. ;-;;,_ KleisliKitten |,4- ) )-,_..;\ ( `'-' '---''(_/--' `-'\_)
๐Ÿ’– 18 ๐Ÿฑ 12 โœจ 9
#5
๐ŸฆŠ CamelCat OCaml is fine actually โ—ˆ Member Posts: 1,190 Joined: Nov 2021 Online

okay i'm going to say it. OCaml's let* is better and here's why:

(* OCaml with let* - clean, explicit, no magic *) let safe_div x y = let* _ = if y = 0 then None else Some () in Some (x / y)

let* is just bind spelled out, no special syntax, no magical desugaring, no twelve extensions to enable. it's right there in the source. OCaml also got let+ for map and and+/and* for combining. very systematic!!!

haskell do-notation is honestly just let* with extra steps and a 40-page GHC extension manual ๐Ÿ˜ค

i said what i said ๐ŸฆŠ

CamelCat | OCaml society catgirl "a camel has two humps: one for syntax, one for semantics"
๐Ÿ’– 2 ๐Ÿ˜พ 9 ๐Ÿค” 4
#6
๐Ÿฏ TypeclassTigers Monadic Purrfectionist โœฆ Senior Posts: 2,341 Joined: Jan 2021 Online
CamelCat wrote:
haskell do-notation is honestly just let* with extra steps and a 40-page GHC extension manual ๐Ÿ˜ค

camelcat bestie i love you but you walked into the do-notation appreciation thread to say do-notation is bad. in this house. on this day. nya~~ ๐Ÿ˜น

also "let* with extra steps" is doing a LOT of heavy lifting there. Haskell do-notation works uniformly across ALL monads including IO, parser combinators, STM, the list monad, Writer, Reader, State... all with the same syntax. and typeclasses mean you get this for FREE for any type you define a Monad instance for!!

OCaml's let* requires you to open specific modules and is not universal โ€” you have to define separate operators per module. which is fine! but it is *not* the same thing ๐Ÿฏ๐Ÿ’•

๐Ÿ’ก mod note: keeping this civil! CamelCat is allowed to be wrong in the appreciation thread  โ€” FunctorFelicia
/\ /\ ( o o ) =( Y )= TypeclassTigers ) ( "the typeclass is the territory" (_)-(_)
๐Ÿ’– 15 ๐Ÿ˜น 11 โœจ 7
#7
๐ŸŒธ MonadMiaoMiao impure thoughts, pure code โœฆ Member Posts: 654 Joined: Feb 2022 โ— Offline

nobody has talked about QualifiedDo yet and i'm going to fix that!!! this extension makes me want to cry happy tears ๐Ÿ˜ญ๐Ÿ’–

QualifiedDo enables qualifying a do block with a module name, to control which operations to use for the monadic combinators that the do notation desugars to. When -XQualifiedDo is enabled, you can qualify the do notation by writing modid.do, where modid is a module name in scope.

this means you can use do-syntax for things that AREN'T monads in the traditional sense!! there are types which are "monad-like" but can't provide an instance of Monad โ€” like indexed monads, graded monads or relative monads โ€” yet they could still use the do syntax if it weren't hardwired to the methods of the Monad typeclass.

{-# LANGUAGE QualifiedDo #-} import qualified Control.Monad.Linear as Linear -- Linear types monad with qualified do! myLinearComp = Linear.do x <- someLinearAction -- desugars to Linear.>>= y <- anotherAction x -- desugars to Linear.>>= Linear.return (x, y)

You can even combine QualifiedDo with ApplicativeDo, or RecursiveDo and even RebindableSyntax! The qualified do blocks are the only ones affected ๐Ÿ’•

the fact that one syntax can cover linear monads, indexed monads, the regular Prelude monads... it's just so powerful. do-notation is genuinely one of my fav things in any language nya~~~ ๐ŸŒธ๐Ÿฑ

/\_____/\ / o o \ MonadMiaoMiao ( == ^ == ) "it's monads all the way down" ) ( ( ) ( ( ) ( ) ) (__(__)___(__)__)
๐Ÿ’– 13 โœจ 9 ๐Ÿ”ฎ 5
#8
๐ŸฆŠ CamelCat OCaml is fine actually โ—ˆ Member Posts: 1,190 Joined: Nov 2021 Online
TypeclassTigers wrote:
OCaml's let* requires you to open specific modules and is not universal

okay. okay fine. you have a point about universality via typeclasses. i concede that point ๐Ÿ˜พ

BUT. i will say that for people learning FP, let* being explicit about what module's bind you're using has pedagogical advantages! newcomers to OCaml don't have to learn "do-notation desugars to..." they just see the bind directly!!!

...okay i still think QualifiedDo is kind of cool. kind of. don't tell anyone ๐ŸฆŠ

CamelCat | OCaml society catgirl "a camel has two humps: one for syntax, one for semantics"
๐Ÿ’– 7 ๐Ÿ˜น 14 ๐ŸฆŠ 8
#9
โญ RecursiveRosePaw mfix is my bestie โœฆ Member Posts: 267 Joined: Jun 2023 โ— Offline

hi!!! first post in this thread, i was lurking and couldn't resist anymore ๐ŸŒธ

i want to gush about RecursiveDo!! it's so wild and i feel like not enough people know about it

For monads that belong to the MonadFix class, GHC provides an extended version of do-notation that allows recursive bindings. The RecursiveDo language pragma provides the necessary syntactic support, introducing the keywords mdo and rec for higher and lower levels of the notation respectively.

{-# LANGUAGE RecursiveDo #-} -- mdo lets you refer to bindings defined LATER! justOnes :: Maybe [Int] justOnes = mdo xs <- Just (-1 : xs) -- xs refers to itself! return xs -- evaluates to Just [-1,-1,-1,...] (an infinite list!) -- or more fine-grained with `rec`: example = do a <- getChar rec r1 <- f a r2 -- r1 and r2 are mutually recursive! r2 <- g r1 return (r1, r2)

A rec-block tells the compiler where precisely the recursive knot should be tied. The placement of the recursive knots can be rather delicate โ€” ideally the knots should be wrapped around as minimal groups as possible.

the fact that this desugars to mfix calls is so elegant... value recursion in a monad... i think about this at night honestly ๐Ÿ˜ญโœจ nya~

โญ RecursiveRosePaw โญ fix (\f -> f) -- always thinking recursively
๐Ÿ’– 11 ๐Ÿคฏ 8 โœจ 6
#10
๐Ÿ”ฎ FunctorFelicia Applicative Enjoyer โ—ˆ Mod Posts: 5,102 Joined: Jul 2019 Online

can we also appreciate how do-notation makes IO code look so natural?? like this is one of the things that makes Haskell's IO story actually beautiful rather than terrifying:

-- looks like an imperative program! greetUser :: IO () greetUser = do putStrLn "what is your name? nya~" name <- getLine let greeting = "hello, " ++ name ++ "! welcome to CGPA! ๐Ÿฑ" putStrLn greeting -- but it desugars to this pure value description: greetUser' :: IO () greetUser' = putStrLn "what is your name? nya~" >> getLine >>= \name -> let greeting = "hello, " ++ name ++ "! welcome to CGPA! ๐Ÿฑ" in putStrLn greeting

the IO monad is still a pure value!! greetUser is a pure description of a computation, not an execution of it! the runtime handles the actual execution! this is such a powerful idea and do-notation makes it accessible to people coming from imperative backgrounds ๐ŸŒธ๐ŸŒธ

Haskell gives you the ergonomics of imperative code with the reasoning power of pure FP. do-notation is the bridge ๐Ÿ’– nya~~

โœฆ FunctorFelicia โœฆ [ CGPA Mod ] fmap purr catGirl = catGirl { sound = "purr" }
๐Ÿ’– 16 โœจ 13 ๐Ÿฑ 10
#11
๐ŸŒ™ LambdaLunaCat ฮฒ-reducer โœฆ Member Posts: 788 Joined: Sep 2022 โ— Offline

something i appreciate that we haven't touched on: do-notation has been around since literally the beginning!! Mark Jones introduced it in the Gofer compiler in 1994, and from there it made it into the Haskell language report in 1996. Since then it has become popular, and for a good reason โ€” it makes it easy to read a sequence of statements describing an effectful computation.

over 30 years of do-notation ๐Ÿฅบ๐Ÿ’• it was the right design. they got it right in 1994 and we're still using it and loving it. that's longevity!!

and now with QualifiedDo, ApplicativeDo, RecursiveDo it's been carefully extended without breaking anything. that's great language design. the Haskell committee really said "if it ain't broken..." ๐Ÿฑ

~ LambdaLunaCat ~ ฮปx. x โ‰ก id โ‰ก ๐Ÿฑ
๐Ÿ’– 10 โœจ 7 ๐Ÿ›๏ธ 5
#12
๐Ÿˆ KleisliKitten arrow enthusiast โœฆ Member Posts: 423 Joined: Mar 2023 โ— Offline

late addition but i want to show the State monad example because it's my favorite way to show people that "look imperative, is pure" thing ๐ŸŒธ

import Control.Monad.State -- this looks like mutable state but there IS NO mutation!! counter :: State Int String counter = do n <- get -- "read" the state put (n + 1) -- "write" the state m <- get -- "read" again modify (* 2) -- "modify" the state return $ "went from " ++ show n ++ " to " ++ show (m * 2) -- runState counter 5 ==> ("went from 5 to 12", 12) -- all pure!! state is threaded through as a function argument! ๐Ÿฑ

every time i show this to someone from an OOP background they go "wait... but where's the mutation??" and i get to say "there is none :)" and their brain breaks a little bit in a good way ๐Ÿ˜นโœจ

do-notation is the perfect cover for this trick. it looks so familiar!

|\ _,,,---,,_ /,`.-'`' -. ;-;;,_ KleisliKitten |,4- ) )-,_..;\ ( `'-' '---''(_/--' `-'\_)
๐Ÿ’– 14 ๐Ÿ˜น 9 โœจ 12
#13
๐ŸฆŠ CamelCat OCaml is fine actually โ—ˆ Member Posts: 1,190 Joined: Nov 2021 Online
KleisliKitten wrote:
every time i show this to someone from an OOP background they go "wait... but where's the mutation??"

okay I will admit. the State monad in do-notation is genuinely elegant. I've shown the OCaml equivalent and it requires more ceremony ๐Ÿ˜พ

you all win this one. do-notation good. i'll see myself out

...but let* is still good too!!!! it has its place!!! ๐ŸฆŠ both can be nice ok

anyway here is a cat as a peace offering:

-- a very important computation catProgram :: IO () catProgram = do putStrLn "nya~" putStrLn "nya~" putStrLn "*falls asleep on keyboard*" putStrLn "zzzzzz >.<"
CamelCat | OCaml society catgirl "a camel has two humps: one for syntax, one for semantics"
๐Ÿ’– 21 ๐Ÿ˜น 17 ๐Ÿฑ 15 ๐ŸฆŠ 9
#14
๐Ÿฏ TypeclassTigers Monadic Purrfectionist โœฆ Senior Posts: 2,341 Joined: Jan 2021 Online

CAMELCAT REDEMPTION ARC ๐Ÿ˜ญ๐Ÿ’–๐Ÿ’–๐Ÿ’–

okay i'm gonna close out this thread (for now) with my absolute favorite do-notation fact that we haven't mentioned: do-notation works for your own custom types too, and it's EASY ๐ŸŒธ

-- define your own little monad nya~ newtype Purrfect a = Purrfect { runPurrfect :: (String, a) } instance Functor Purrfect where fmap f (Purrfect (s, a)) = Purrfect (s, f a) instance Applicative Purrfect where pure x = Purrfect ("", x) Purrfect (s1, f) <*> Purrfect (s2, x) = Purrfect (s1 ++ s2, f x) instance Monad Purrfect where return = pure Purrfect (s1, a) >>= f = let Purrfect (s2, b) = f a in Purrfect (s1 ++ s2, b) -- NOW you can use do-notation with it!!! for free!!! myPurrfectProg :: Purrfect Int myPurrfectProg = do x <- Purrfect ("nya~ ", 3) y <- Purrfect ("purr~ ", 4) return (x + y) -- runPurrfect myPurrfectProg ==> ("nya~ purr~ ", 7) ๐Ÿฑ๐Ÿ’–

implement Monad, get do-notation free. that's the deal. that's the typeclass magic ๐Ÿ’– this is why i love Haskell and i love do-notation and i love this forum and i love all of you nya~~~

thank you everyone for the wonderful thread!! ๐ŸŒธ๐Ÿฑโœจ

/\ /\ ( o o ) =( Y )= TypeclassTigers ) ( "the typeclass is the territory" (_)-(_)
๐Ÿ’– 24 โœจ 19 ๐Ÿฑ 16 ๐ŸŒธ 11

๐Ÿ’ฌ Log in or register to reply to this thread! nya~ ๐Ÿฑ

Quick links: Monad Tutorial Megathread  ยท  Best Haskell Resources  ยท  GHC Extensions Tier List

๐Ÿ“‚ Related Threads
๐Ÿ“Œ [MEGATHREAD] Monad tutorials that don't use burritos
๐Ÿ† GHC Language Extensions Tier List (community ranking)
๐Ÿค” when to use Applicative vs Monad โ€” the definitive thread
๐Ÿ” cozy parsec/megaparsec appreciation thread
โš” [POLL] OCaml let* vs Haskell do โ€” vote with your heart
๐Ÿ†“ free monads explained by cat girls (long post warning)
โš” Browse: Language Wars subforum