
Monads need syntax: a Go deep read
A deep read of Omer Preminger's argument that monads only pay off in Go when the language gives them native-feeling syntax. The issue explains why `Result` feels natural in Rust but awkward as a plain Go library abstraction, and what that means for importing FP ideas into mainstream codebases.
Go already has a stubbornly effective error story: return a value plus an error, check it, keep moving. Omer Preminger's July 5, 2026 post argues that dropping Rust-style
Result into that world does not buy much unless the language gives the abstraction syntax that makes it feel ordinary. 1Recommended post
Original: Monads need syntactic sugar, by Omer Preminger, published July 5, 2026. Preminger frames the piece around a practical Go question: why does
samber/lo feel useful in his Go work, while samber/mo keeps tempting him but never sticks? 1That makes this a good first deep read for the channel. It sits exactly at the seam between FP theory and mainstream-language practice: not "what is a monad?" but "what has to be true before monads improve day-to-day code?"
The post in one sentence
Preminger's claim is that monads are not portable as library objects alone. They need host-language support at the call site. Without that support, Go's conventional
(value, error) style can be just as clear, with fewer new dependencies and fewer unfamiliar idioms. 1Full read-through
The post opens with a friendly contradiction. Preminger says he is a big fan of both functional programming and Go, then points to
samber/lo as one of his favorite Go modules. The library describes itself as a Lodash-style Go package built on Go 1.18+ generics, with helpers such as map, filter, find, and related collection utilities. 2That part feels natural in Go because
lo.Map is still close to the grain of ordinary Go: pass a slice, pass a function, receive a slice. The syntax is slightly more functional, but the mental model is not alien.The paired library
samber/mo is a different proposition. Its README says it brings monads and FP abstractions to Go projects, including Option, Result, Either, Future, IO, Task, and State. 3 Preminger's attraction to it is easy to understand if you have moved between Rust and Go. Rust's Option and Result are useful partly because they make absence and failure explicit in the type system.His objection is that Go does not give
Result the same syntactic treatment Rust does. In Rust, error propagation and result handling are woven into the language. In Go, the mo.Result example still leaves the programmer checking whether the result is an error, then extracting either the error or the value. Preminger compares that with the ordinary Go version that returns (int, error) and finds the shapes nearly identical. 1That comparison is the post's hinge. The monad has changed the type vocabulary, but not the local experience of writing or reading the code. If the call-site ceremony is the same, the abstraction has to justify its own weight.
Preminger does not say monads are useless in Go. He carves out a real exception: when monads appear inside FP-style collection transformations,
Result can compose with lo.Map in a way that feels more meaningful. 1 The problem is frequency. In his own work, those cases are too rare to pay for a non-standard pattern across a Go codebase.The post ends with a pragmatic verdict:
lo stays, mo does not. It is not an anti-FP take. It is a host-language-design take. An FP abstraction has to cooperate with the syntax, conventions, and maintenance culture of the language it is entering.The important technical detail
The strongest idea in the post is that syntax is not cosmetic. Syntax decides whether an abstraction stays visible.
Go gives us a useful contrast. In Go 1.23, the language added range-over-function support and standardized iterator forms such as
iter.Seq and iter.Seq2, so user-defined iteration can plug into for/range. 4 That is a small but telling example: once the language gives an abstraction a native use site, the abstraction can recede into normal code.Result in Go does not have that privilege. There is no Rust-like ? operator, no do-notation, and no standard pattern that makes the happy path read as the main path. The result is that the monad remains visible at every step. You see the wrapper. You see the extraction. You see the check.That visibility is not automatically bad. It can be excellent when a team wants the failure model to be explicit in types. But it changes the trade-off. A monad library is no longer just a safer error type; it is a local dialect your team has to teach, review, and defend.
What the post gets right
Preminger's argument is useful because it avoids the usual religious split: "Go is too simple for FP" versus "Go should import more FP concepts." The better question is narrower: where does the host language already provide an ergonomic slot for the idea?
lo has a slot. Go developers already understand slices, functions, and generic helpers. A library call such as lo.Map is explicit, but it does not ask the rest of the program to reorganize itself.mo.Result asks for more. It competes with one of Go's most entrenched idioms: return an error and check it immediately. To win that fight, it would need more than theoretical elegance. It would need to make the common path shorter, clearer, or safer in a way readers feel every day.That is the portable lesson for FP in mainstream languages. A functional abstraction succeeds when it disappears into the host language's ordinary motion. It struggles when every use reminds the reader that a foreign abstraction has arrived.
How to apply it
Use Preminger's post as a quick test before importing an FP abstraction into a non-FP codebase:
- Does the host language have syntax for it? If yes, the abstraction may feel native. If no, expect visible ceremony.
- Does it improve the common path or only the rare path? A rare improvement may not justify a permanent dependency.
- Can ordinary maintainers debug it without a glossary? If the answer is no, keep it at module boundaries or in small, expert-owned areas.
- Does it compose with something the code already does often?
MapandFiltercompose with slices.Resultonly pays off when failures themselves are being transformed or combined.
None of this argues against FP. It argues for respecting the language you are actually using.
Lines worth keeping
Preminger's shortest summary of the comparison is also the clearest:
"That is... almost exactly the same."
The main claim follows immediately after:
"And that is the crux of the matter: absent the Rust-style syntactic sugar that exists forResult, usingsamber/moin Go really doesn't buy us very much."
His practical cost-benefit test is blunt:
"The juice is just not worth the squeeze."
And the final verdict keeps the piece from becoming an anti-monad rant:
"Monads are awesome, but they provide an advantage over Go's(SOMETYPE, error)convention only if there is syntactic sugar to support them (à la Rust)." 1
Bottom line
This is a small post with a large design lesson. FP ideas do not travel as pure concepts. They travel through syntax, error conventions, library norms, and team habits. If those layers cooperate, an abstraction can make code simpler. If they do not, even a good abstraction can become one more thing the reader has to mentally unwrap.
Fuentes de referencia
Contenido relacionado
- Inicia sesión para comentar.
