# Advanced Patterns ## Composing Transformations Chain lo functions to build multi-step pipelines. Each function returns a new collection that feeds into the next. ```go // Pipeline: extract active user emails grouped by role emailsByRole := lo.GroupBy( lo.Map( lo.Filter(users, func(u User, _ int) bool { return u.Active && u.EmailVerified }), func(u User, _ int) UserEmail { return UserEmail{Role: u.Role, Email: u.Email} }, ), func(ue UserEmail, _ int) string { return ue.Role }, ) ``` For long chains, break into named intermediate variables for readability: ```go active := lo.Filter(users, func(u User, _ int) bool { return u.Active }) names := lo.Map(active, func(u User, _ int) string { return u.Name }) unique := lo.Uniq(names) ``` ## lo + stdlib Interop Prefer stdlib when it covers the operation — `lo` adds value for functional transforms the stdlib doesn't provide. | Operation | stdlib (prefer) | lo (use when stdlib lacks) | | --- | --- | --- | | Contains | `slices.Contains(s, v)` | `lo.ContainsBy(s, fn)` — predicate-based | | Sort | `slices.SortFunc(s, cmp)` | — (lo doesn't provide sort) | | Keys | `maps.Keys(m)` | `lo.UniqKeys(m)` — deduplicated keys | | Clone | `slices.Clone(s)` | `lo.Map(s, fn)` — when you need transform during clone | | Min/Max | `slices.Min(s)` | `lo.MinBy(s, fn)` — by extractor function | **Rule of thumb:** If `slices.*` or `maps.*` does what you need, use it. Reach for `lo` when you need predicates, transforms, grouping, or error variants. ## lo + samber/mo Integration `samber/mo` provides monadic types (Option, Result, Either). They compose naturally with lo: ```go // Filter users with valid optional emails validEmails := lo.FilterMap(users, func(u User, _ int) (string, bool) { email, ok := u.Email.Get() // mo.Option[string] return email, ok }) // Map with Result — collect successes results := lo.FilterMap(urls, func(url string, _ int) (Response, bool) { res := fetchURL(url) // returns mo.Result[Response] val, err := res.Get() return val, err == nil }) ``` ## Iterator Patterns (loi) Requires Go 1.23+. Lazy iterators avoid intermediate allocations. ### Eager vs lazy comparison ```go // Eager — allocates 2 intermediate slices result := lo.Map(lo.Filter(bigSlice, filterFn), mapFn) // Lazy — zero intermediate allocations for v := range loi.Map(loi.Filter(bigSlice, filterFn), mapFn) { process(v) } ``` ### Building lazy pipelines ```go // Lazy pipeline: filter → map → take first 10 pipeline := loi.Take( loi.Map( loi.Filter(records, func(r Record) bool { return r.Score > 0.8 }), func(r Record) string { return r.Name }, ), 10, ) // Consume with range for name := range pipeline { fmt.Println(name) } ``` ## Performance-Sensitive Patterns ### When to switch from lo to lom **Trigger:** `go tool pprof -alloc_objects` shows `lo.Filter` or `lo.Map` as top allocators in a hot path. ```go // Before — allocates new slice every call filtered := lo.Filter(events, isValid) // After — zero allocations, modifies in-place events = lom.Filter(events, isValid) // Warning: 'events' is now modified. Original data is lost. ``` ### Parallel transforms **Trigger:** `go tool pprof -cpu` shows transform function dominating CPU on large datasets. ```go // Switch from sequential to parallel results := lop.Map(largeSlice, expensiveTransform) ``` ## Testing with lo lo helpers simplify test data generation and assertions: ```go // Generate test fixtures users := lo.Times(100, func(i int) User { return User{ID: i, Name: fmt.Sprintf("user-%d", i)} }) // Assert subset relationships assert.True(t, lo.Every(expected, lo.Map(actual, extractID))) // Generate random test data ids := lo.Times(50, func(_ int) string { return lo.RandomString(16, lo.AlphanumericCharset) }) // Quick frequency check counts := lo.CountValues(results) assert.Equal(t, 3, counts["success"]) ``` ## Slice-to-Map Conversion Common pattern: convert a slice into a lookup map. ```go // By ID userByID := lo.SliceToMap(users, func(u User) (int, User) { return u.ID, u }) // By key function userByEmail := lo.KeyBy(users, func(u User) string { return u.Email }) // Filter + convert in one pass activeByID := lo.FilterSliceToMap(users, func(u User) (int, User, bool) { return u.ID, u, u.Active }) ```