Couldn’t you do …funcs like here: https://pkg.go.dev/slices#MaxFunc and 
then use the cmp package for primitive types and for structs you could use 
https://go.dev/ref/spec#Method_expressions

[email protected] schrieb am Donnerstag, 25. Juni 2026 um 03:16:30 UTC+2:

> *TL;DR:* Go 1.27's generic methods (golang/go#77273) finally make 
> left-to-right,
> chainable, lazy collection pipelines expressible. I built a library to 
> explore
> what falls out of that, and the most interesting part turned out to be 
> where
> generic methods *still* can't help — which draws a surprisingly clean line
> through the whole API. Repo at the bottom; I'd genuinely like the list's 
> view
> on the design choices.
>
>
> *THE THING I HATED*
>
> Anyone who has used samber/lo has written something like this:
>
>     sum := lo.Sum(
>         lo.Map(
>             lo.Filter(xs, func(x int, _ int) bool { return x%2 == 0 }),
>             func(x int, _ int) int { return x * x },
>         ),
>     )
>
> The logic is filter -> map -> sum, but you read it sum(map(filter(...))):
> inside-out, the reverse of the data flow. For years I assumed this was a 
> taste
> decision lo had made. It wasn't.
>
> Before 1.27, a method could not declare its own type parameters. That 
> means a
> .Map() that turns Seq[int] into Seq[string] was not merely discouraged, it 
> was
> unrepresentable:
>
>     // pre-1.27: does not compile. A method may not introduce [U any].
>     func (s Seq[T]) Map[U any](f func(T) U) Seq[U]
>
> Because methods couldn't carry type parameters, every library of this kind 
> was
> forced into top-level functions, and top-level functions compose 
> inside-out.
> The nesting wasn't a style; it was the language.
>
>
> *WHAT 1.27 UNLOCKS*
>
> golang/go#77273 (accepted and implemented in 1.27) lifts that restriction. 
> The
> same pipeline becomes:
>
>     sum := From(xs).
>         Filter(func(x int) bool { return x%2 == 0 }).
>         SumBy(func(x int) int { return x * x })
>
> Left-to-right, in data-flow order, and it autocompletes after the dot. It 
> is
> also lazy and short-circuiting, because it is built directly on the stdlib
> iterators.
>
>
> *THE DESIGN, AND THE PART THAT SURPRISED ME*
>
> The core types are *defined types* over iter.Seq, not struct wrappers:
>
>     type Seq[T any]     iter.Seq[T]      // func(yield func(T) bool)
>     type Seq2[K, V any] iter.Seq2[K, V]
>
> This gives zero-cost conversion in both directions: any iter.Seq[T] is 
> usable
> as a Seq[T] and the result feeds straight into slices.Collect, maps.Keys, 
> etc.
> But it has a consequence I didn't anticipate.
>
> Because Seq[T any] pins T to `any` at the type level, a method's own type
> parameters are fresh and independent — they cannot add a constraint back 
> onto
> the receiver's T. So even *with* generic methods, you cannot write:
>
>     func (s Seq[T]) Distinct() Seq[T]      // needs T comparable  -> 
> impossible
>     func (s Seq[T]) Max() (T, bool)        // needs cmp.Ordered   -> 
> impossible
>
> Anything that constrains T itself must remain a free function:
>
>     func Distinct[T comparable](s Seq[T]) Seq[T]
>     func Max[T cmp.Ordered](s Seq[T]) (T, bool)
>     func Sum[T Numeric](s Seq[T]) T
>
> But anything that only uses a method's *own* constrained parameter can be a
> method (the escape hatch):
>
>     func (s Seq[T]) GroupBy[K comparable](key func(T) K) map[K][]T
>     func (s Seq[T]) Map[U any](f func(T) U) Seq[U]
>
> So the whole API splits along one mechanically checkable question: does the
> operation constrain T itself? If yes, free function; if no, method. Generic
> methods solved most of the problem and drew a hard, principled line at the
> rest. I found that line more interesting than the feature itself.
>
>
> *RECOVERING THE CHAIN*
>
> The annoying corollary: Distinct/Max/Sum being free functions drags you 
> back to
> inside-out reading, Sum(Distinct(xs)). The least-ugly fix I found is to 
> pin the
> constraint onto a subtype up front:
>
>     type SeqNumeric[T Numeric] iter.Seq[T]
>     func Numbers[T Numeric](s Seq[T]) SeqNumeric[T]
>
>     // one entry function crosses the constraint boundary; then it's 
> methods
>     sum := Numbers(From(xs)).Distinct().Sum()
>
> Inside SeqNumeric the constraint is already satisfied, so Distinct/Sum 
> become
> methods again. Numeric ⊂ Ordered ⊂ comparable lets you downgrade with
> .Ordered()/.Comparable(). It works, but it is plainly a workaround for "a 
> method
> that adds a constraint to its receiver," which the type system cannot 
> express.
>
>
> *HONEST LIMITATIONS*
>
> - Requgoog_1145710296ires Go 1.27 (currently go1.27rc1). The method chain 
> depends on #77273,
>   which is accepted and implemented but not yet in a stable release.
> - Not evegoog_1145710297rything is lazy. Operations that must see the 
> whole sequence (Sort,
>   Reverse, TakeRight, sliding Window) materialize internally; they're 
> isolated
>   in one file and documented as such.
> - Chunk/Window return iter.Seq[Seq[T]] rather than Seq[Seq[T]]: on 1.27rc1 
> a
>   generic method on Seq[T] can't instantiate Seq[Seq[T]] (instantiation 
> cycle,
>   T := Seq[T]). I'd love to know if this is expected to relax in the stable
>   release.
> - gofmt on 1.27rc1 flags generic-method signature lines with "method must 
> have
>   no type parameters" — appears to be a formatter/parser lag; build, vet, 
> and
>   test all pass. Can anyone confirm this is a known rc issue?
> - Out of scope by choice: error-handling chains (Seq2[T, error] 
> short-circuit —
>   I couldn't find a clean fit), parallelism, in-place mutation, 
> arbitrary-depth
>   flatten, tuples past Tuple4.
>
>
>
> *WHAT I'D LIKE FEEDBACK ON*
> 1. Defined type over iter.Seq vs a struct wrapper — does the zero-cost 
> interop
>    justify pinning T to `any` and pushing constrained ops to free 
> functions?
> 2. The method-vs-free-function rule above: would you have drawn it 
> elsewhere?
> 3. Any cleaner idea than the subtype trick for restoring the chain on
>    constrained operations?
>
> Repo, with the full op set (~80: Map/FlatMap/Scan/Zip/GroupBy/Window/set 
> ops/
> Seq2), and a tasks/ directory containing the PRD and design rationale:
>
>     https://github.com/smallnest/seq
>
> Thanks for reading — happy to be told I've gotten any of this wrong.
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/golang-nuts/daa7881a-7374-477c-a167-5410a9c8f353n%40googlegroups.com.

Reply via email to