
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.
Result into that world does not buy much unless the language gives the abstraction syntax that makes it feel ordinary. 1samber/lo feel useful in his Go work, while samber/mo keeps tempting him but never sticks? 1(value, error) style can be just as clear, with fewer new dependencies and fewer unfamiliar idioms. 1samber/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. 2lo.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.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.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. 1Result 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.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.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.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.Map and Filter compose with slices. Result only pays off when failures themselves are being transformed or combined."That is... almost exactly the same."
"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."
"The juice is just not worth the squeeze."
"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
preminger.xyz
github.com
github.com
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.