Haskell中monadic bind为何是左结合?>>=、>>与do语法的疑惑
Great question—this is one of those subtle design choices that clicks once you connect the dots between Haskell's syntax rules, operator conventions, and monad laws. Let's break this down step by step.
First: Operator Associativity ≠ Semantic Order
First off, let's clarify a key point: associativity rules are about how the compiler parses unparenthesized expressions, not about the order of execution. When we say >>= and >> are infixl 1, that just means the compiler reads a >> b >> c as ((a >> b) >> c) instead of a >> (b >> c). But for monadic sequencing (where >> means "do this, then do that"), the execution order is always left-to-right regardless of associativity—because the semantics of >> enforce that the left action runs first, then the right one.
The Monad Law That Makes This Work
The apparent "discrepancy" between do-notation desugaring and operator associativity vanishes thanks to the associativity law of monads, which is one of the three mandatory laws for any valid Monad instance:
(m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
In plain terms: chaining binds left-to-right (via operator associativity) is semantically identical to nesting them right-to-left (like do-notation desugars to). For your specific example with >> (which is just >>= const), this simplifies to:
((a >> b) >> c) ≡ a >> (b >> c)
So even though the parse trees look different, the runtime behavior is exactly the same. That's why Haskell can safely have left-associative operators while do-notation desugars to right-nested calls—they're guaranteed to be equivalent by monad laws.
Why Left-Associative? Convention and Intuition
If the semantics are equivalent either way, why choose left associativity? It boils down to familiarity and consistency with standard operator behavior:
- Most arithmetic and sequential operators in programming languages are left-associative (e.g.,
1 + 2 + 3is parsed as((1 + 2) + 3)). Haskell follows this convention so thata >> b >> cfeels natural to write and read, without needing extra parentheses. - For bind operations with arguments (
>>=), left associativity aligns with how we think about sequential steps. Writinga >>= f >>= greads like "runa, pass its result tof, then pass that result tog"—which matches the linear flow of do-notation, even if the parse structure is nested differently.
A Concrete Example
Let's use the IO monad to see this in action. Consider this do-block:
do putStrLn "First" putStrLn "Second" putStrLn "Third"
It desugars to:
putStrLn "First" >> (putStrLn "Second" >> putStrLn "Third")
But if we write the unparenthesized operator chain:
putStrLn "First" >> putStrLn "Second" >> putStrLn "Third"
The compiler parses it as:
((putStrLn "First" >> putStrLn "Second") >> putStrLn "Third")
Thanks to the monad associativity law, both versions will print the three lines in the exact same order. No difference in behavior—only in how the code is parsed.
Wrapping Up
The left-associative design for >>= and >> is a pragmatic choice: it fits with standard operator conventions, makes unparenthesized chains intuitive to write, and the monad associativity law ensures that it's semantically equivalent to the right-nested structure of desugared do-notation. The "conflict" you noticed is just a surface-level syntax difference—under the hood, everything lines up perfectly.
内容的提问来源于stack exchange,提问作者Neil Mitchell

