github.com/m4gshm/gollections@v0.0.13-0.20240331203319-a34a86e58a24/README.md (about)

     1  # Gollections
     2  
     3  Gollections is set of functions for [slices](#slices), [maps](#maps) and
     4  additional implementations of data structures such as [ordered
     5  map](#mutable-collections) or [set](#mutable-collections) aimed to
     6  reduce boilerplate code.
     7  
     8  Supports Go version 1.21.
     9  
    10  For example, it’s need to group some
    11  [users](./internal/examples/boilerplate/user_type.go) by their role
    12  names converted to lowercase:
    13  
    14  ``` go
    15  var users = []User{
    16      {name: "Bob", age: 26, roles: []Role{{"Admin"}, {"manager"}}},
    17      {name: "Alice", age: 35, roles: []Role{{"Manager"}}},
    18      {name: "Tom", age: 18},
    19  }
    20  ```
    21  
    22  You can make clear code, extensive, but without dependencies:
    23  
    24  ``` go
    25  var namesByRole = map[string][]string{}
    26  add := func(role string, u User) {
    27      namesByRole[role] = append(namesByRole[role], u.Name())
    28  }
    29  for _, u := range users {
    30      if roles := u.Roles(); len(roles) == 0 {
    31          add("", u)
    32      } else {
    33          for _, r := range roles {
    34              add(strings.ToLower(r.Name()), u)
    35          }
    36      }
    37  }
    38  //map[:[Tom] admin:[Bob] manager:[Bob Alice]]
    39  ```
    40  
    41  Or you can write more compact code using the collections API, like:
    42  
    43  ``` go
    44  import "github.com/m4gshm/gollections/slice/convert"
    45  import "github.com/m4gshm/gollections/slice/group"
    46  
    47  var namesByRole = group.ByMultipleKeys(users, func(u User) []string {
    48      return convert.AndConvert(u.Roles(), Role.Name, strings.ToLower)
    49  }, User.Name)
    50  // map[:[Tom] admin:[Bob] manager:[Bob Alice]]
    51  ```
    52  
    53  ## Installation
    54  
    55  ``` console
    56  go get -u github.com/m4gshm/gollections
    57  ```
    58  
    59  ## Slices
    60  
    61  ``` go
    62  data, err := slice.Conv(slice.Of("1", "2", "3", "4", "_", "6"), strconv.Atoi)
    63  even := func(i int) bool { return i%2 == 0 }
    64  
    65  result := slice.Reduce(slice.Convert(slice.Filter(data, even), strconv.Itoa), op.Sum)
    66  
    67  assert.ErrorIs(t, err, strconv.ErrSyntax)
    68  assert.Equal(t, "24", result)
    69  ```
    70  
    71  In the example is used only small set of slice functions as
    72  [slice.Filter](#slicefilter), [slice.Conv](#sliceconv)
    73  [slice.Convert](#sliceconvert#), and [slice.Reduce](#slicereduce). More
    74  you can look in the [slice](./slice/api.go) package.
    75  
    76  ### Shortcut packages
    77  
    78  ``` go
    79  result := sum.Of(filter.AndConvert(data, even, strconv.Itoa))
    80  ```
    81  
    82  This is a shorter version of the previous example that used short
    83  aliases [sum.Of](#sumof) and
    84  [filter.AndConvert](#operations-chain-functions). More shortcuts you can
    85  find by exploring slices [subpackages](./slice).
    86  
    87  **Be careful** when use several slice functions subsequently like
    88  `slice.Filter(slice.Convert(…​))`. This can lead to unnecessary RAM
    89  consumption. Consider
    90  [loop](#loop-kvloop-and-breakable-versions-breakloop-breakkvloop)
    91  instead of slice API.
    92  
    93  ### Main slice functions
    94  
    95  #### Instantiators
    96  
    97  ##### slice.Of
    98  
    99  ``` go
   100  var s = slice.Of(1, 3, -1, 2, 0) //[]int{1, 3, -1, 2, 0}
   101  ```
   102  
   103  ##### range\_.Of
   104  
   105  ``` go
   106  import "github.com/m4gshm/gollections/slice/range_"
   107  
   108  var increasing = range_.Of(-1, 3)    //[]int{-1, 0, 1, 2}
   109  var decreasing = range_.Of('e', 'a') //[]rune{'e', 'd', 'c', 'b'}
   110  var nothing = range_.Of(1, 1)        //nil
   111  ```
   112  
   113  ##### range\_.Closed
   114  
   115  ``` go
   116  var increasing = range_.Closed(-1, 3)    //[]int{-1, 0, 1, 2, 3}
   117  var decreasing = range_.Closed('e', 'a') //[]rune{'e', 'd', 'c', 'b', 'a'}
   118  var one = range_.Closed(1, 1)            //[]int{1}
   119  ```
   120  
   121  #### Sorters
   122  
   123  ##### sort.Asc, sort.Desc
   124  
   125  ``` go
   126  // sorting in-place API
   127  import "github.com/m4gshm/gollections/slice/sort"
   128  
   129  var ascendingSorted = sort.Asc([]int{1, 3, -1, 2, 0})   //[]int{-1, 0, 1, 2, 3}
   130  var descendingSorted = sort.Desc([]int{1, 3, -1, 2, 0}) //[]int{3, 2, 1, 0, -1}
   131  ```
   132  
   133  ##### sort.By, sort.ByDesc
   134  
   135  ``` go
   136  // sorting copied slice API does not change the original slice
   137  import "github.com/m4gshm/gollections/slice/clone/sort"
   138  
   139  // see the User structure above
   140  var users = []User{
   141      {name: "Bob", age: 26},
   142      {name: "Alice", age: 35},
   143      {name: "Tom", age: 18},
   144      {name: "Chris", age: 41},
   145  }
   146  
   147  var byName = sort.By(users, User.Name)
   148  //[{Alice 35 []} {Bob 26 []} {Chris 41 []} {Tom 18 []}]
   149  
   150  var byAgeReverse = sort.DescBy(users, User.Age)
   151  //[{Chris 41 []} {Alice 35 []} {Bob 26 []} {Tom 18 []}]
   152  ```
   153  
   154  #### To map converters
   155  
   156  ##### group.Of
   157  
   158  ``` go
   159  import "github.com/m4gshm/gollections/convert/as"
   160  import "github.com/m4gshm/gollections/expr/use"
   161  import "github.com/m4gshm/gollections/slice/group"
   162  
   163  var ageGroups = group.Of(users, func(u User) string {
   164      return use.If(u.age <= 20, "<=20").If(u.age <= 30, "<=30").Else(">30")
   165  }, as.Is)
   166  
   167  //map[<=20:[{Tom 18 []}] <=30:[{Bob 26 []}] >30:[{Alice 35 []} {Chris 41 []}]]
   168  ```
   169  
   170  ##### group.ByMultipleKeys
   171  
   172  ``` go
   173  import "github.com/m4gshm/gollections/slice/convert"
   174  import "github.com/m4gshm/gollections/slice/group"
   175  
   176  var namesByRole = group.ByMultipleKeys(users, func(u User) []string {
   177      return convert.AndConvert(u.Roles(), Role.Name, strings.ToLower)
   178  }, User.Name)
   179  // map[:[Tom] admin:[Bob] manager:[Bob Alice]]
   180  ```
   181  
   182  ##### slice.ToMap, slice.ToMapResolv
   183  
   184  ``` go
   185  import (
   186      "github.com/m4gshm/gollections/map_/resolv"
   187      "github.com/m4gshm/gollections/op"
   188      "github.com/m4gshm/gollections/slice"
   189  )
   190  
   191  var ageGroupedSortedNames map[string][]string
   192  
   193  ageGroupedSortedNames = slice.ToMapResolv(users, func(u User) string {
   194      return op.IfElse(u.age <= 30, "<=30", ">30")
   195  }, User.Name, resolv.SortedSlice)
   196  
   197  //map[<=30:[Bob Tom] >30:[Alice Chris]]
   198  ```
   199  
   200  #### Reducers
   201  
   202  ##### sum.Of
   203  
   204  ``` go
   205  import "github.com/m4gshm/gollections/op/sum"
   206  
   207  var sum = sum.Of(1, 2, 3, 4, 5, 6) //21
   208  ```
   209  
   210  ##### slice.Reduce
   211  
   212  ``` go
   213  var sum = slice.Reduce([]int{1, 2, 3, 4, 5, 6}, func(i1, i2 int) int { return i1 + i2 })
   214  //21
   215  ```
   216  
   217  ##### slice.First
   218  
   219  ``` go
   220  import "github.com/m4gshm/gollections/predicate/more"
   221  import "github.com/m4gshm/gollections/slice"
   222  
   223  result, ok := slice.First([]int{1, 3, 5, 7, 9, 11}, more.Than(5)) //7, true
   224  ```
   225  
   226  ##### slice.Last
   227  
   228  ``` go
   229  import "github.com/m4gshm/gollections/predicate/less"
   230  import "github.com/m4gshm/gollections/slice"
   231  
   232  result, ok := slice.Last([]int{1, 3, 5, 7, 9, 11}, less.Than(9)) //7, true
   233  ```
   234  
   235  #### Converters
   236  
   237  ##### slice.Convert
   238  
   239  ``` go
   240  var s []string = slice.Convert([]int{1, 3, 5, 7, 9, 11}, strconv.Itoa)
   241  //[]string{"1", "3", "5", "7", "9", "11"}
   242  ```
   243  
   244  ##### slice.Conv
   245  
   246  ``` go
   247  result, err := slice.Conv(slice.Of("1", "3", "5", "_7", "9", "11"), strconv.Atoi)
   248  //[]int{1, 3, 5}, ErrSyntax
   249  ```
   250  
   251  ##### slice.Filter
   252  
   253  ``` go
   254  import "github.com/m4gshm/gollections/predicate/exclude"
   255  import "github.com/m4gshm/gollections/predicate/one"
   256  import "github.com/m4gshm/gollections/slice"
   257  
   258  var f1 = slice.Filter([]int{1, 3, 5, 7, 9, 11}, one.Of(1, 7).Or(one.Of(11))) //[]int{1, 7, 11}
   259  var f2 = slice.Filter([]int{1, 3, 5, 7, 9, 11}, exclude.All(1, 7, 11))       //[]int{3, 5, 9}
   260  ```
   261  
   262  ##### slice.Flat
   263  
   264  ``` go
   265  import "github.com/m4gshm/gollections/convert/as"
   266  import "github.com/m4gshm/gollections/slice"
   267  
   268  var i []int = slice.Flat([][]int{{1, 2, 3}, {4}, {5, 6}}, as.Is)
   269  //[]int{1, 2, 3, 4, 5, 6}
   270  ```
   271  
   272  #### Operations chain functions
   273  
   274  - convert.AndReduce, conv.AndReduce
   275  
   276  - convert.AndFilter
   277  
   278  - filter.AndConvert
   279  
   280  These functions combine converters, filters and reducers.
   281  
   282  ## Maps
   283  
   284  ### Main map functions
   285  
   286  #### Instantiators
   287  
   288  ##### clone.Of
   289  
   290  ``` go
   291  import "github.com/m4gshm/gollections/map_/clone"
   292  
   293  var bob = map[string]string{"name": "Bob"}
   294  var tom = map[string]string{"name": "Tom"}
   295  
   296  var employers = map[string]map[string]string{
   297      "devops": bob,
   298      "jun":    tom,
   299  }
   300  
   301  copy := clone.Of(employers)
   302  delete(copy, "jun")
   303  bob["name"] = "Superbob"
   304  
   305  fmt.Printf("%v\n", employers) //map[devops:map[name:Superbob] jun:map[name:Tom]]
   306  fmt.Printf("%v\n", copy)      //map[devops:map[name:Superbob]]
   307  ```
   308  
   309  ##### clone.Deep
   310  
   311  ``` go
   312  import "github.com/m4gshm/gollections/map_/clone"
   313  
   314  var bob = map[string]string{"name": "Bob"}
   315  var tom = map[string]string{"name": "Tom"}
   316  
   317  var employers = map[string]map[string]string{
   318      "devops": bob,
   319      "jun":    tom,
   320  }
   321  
   322  copy := clone.Deep(employers, func(employer map[string]string) map[string]string {
   323      return clone.Of(employer)
   324  })
   325  delete(copy, "jun")
   326  bob["name"] = "Superbob"
   327  
   328  fmt.Printf("%v\n", employers) //map[devops:map[name:Superbob] jun:map[name:Tom]]
   329  fmt.Printf("%v\n", copy)      //map[devops:map[name:Bob]]
   330  ```
   331  
   332  #### Keys, values exrtractors
   333  
   334  ##### map\_.Keys, map\_.Values
   335  
   336  ``` go
   337  var employers = map[string]map[string]string{
   338      "devops": {"name": "Bob"},
   339      "jun":    {"name": "Tom"},
   340  }
   341  
   342  keys := map_.Keys(employers)     //[devops jun]
   343  values := map_.Values(employers) //[map[name:Bob] map[name:Tom]]
   344  ```
   345  
   346  #### Converters
   347  
   348  ##### map\_.ConvertKeys
   349  
   350  ``` go
   351  var keys = map_.ConvertKeys(employers, func(title string) string {
   352      return string([]rune(title)[0])
   353  })
   354  //map[d:map[name:Bob] j:map[name:Tom]]
   355  ```
   356  
   357  ##### map\_.ConvertValues
   358  
   359  ``` go
   360  var vals = map_.ConvertValues(employers, func(employer map[string]string) string {
   361      return employer["name"]
   362  })
   363  //map[devops:Bob jun:Tom]
   364  ```
   365  
   366  ##### map\_.Convert
   367  
   368  ``` go
   369  var all = map_.Convert(employers, func(title string, employer map[string]string) (string, string) {
   370      return string([]rune(title)[0]), employer["name"]
   371  })
   372  //map[d:Bob j:Tom]
   373  ```
   374  
   375  ##### map\_.Conv
   376  
   377  ``` go
   378  var all, err = map_.Conv(employers, func(title string, employer map[string]string) (string, string, error) {
   379      return string([]rune(title)[0]), employer["name"], nil
   380  })
   381  //map[d:Bob j:Tom], nil
   382  ```
   383  
   384  ##### map\_.ToSlice
   385  
   386  ``` go
   387  var users = map_.ToSlice(employers, func(title string, employer map[string]string) User {
   388      return User{name: employer["name"], roles: []Role{{name: title}}}
   389  })
   390  //[{name:Bob age:0 roles:[{name:devops}]} {name:Tom age:0 roles:[{name:jun}]}]
   391  ```
   392  
   393  ## [loop](./loop/api.go), [kv/loop](./kv/loop/api.go) and breakable versions [break/loop](./break/loop/api.go), [break/kv/loop](./break/kv/loop/api.go)
   394  
   395  Low-level API for iteration based on next functions:
   396  
   397  ``` go
   398  type (
   399      Loop[T any]           func() (element T, ok bool)
   400      KVLoop[K, V any]      func() (key K, value V, ok bool)
   401      BreakLoop[T any]      func() (element T, ok bool, err error)
   402      BreakKVLoop[K, V any] func() (key K, value V, ok bool, err error)
   403  )
   404  ```
   405  
   406  The `Loop` function retrieves a next element from a dataset and returns
   407  `ok==true` if successful.  
   408  The `KVLoop` behaves similar but returns a key/value pair.  
   409  
   410  ``` go
   411  even := func(i int) bool { return i%2 == 0 }
   412  stringSeq := loop.Convert(loop.Filter(loop.Of(1, 2, 3, 4), even), strconv.Itoa)
   413  
   414  assert.Equal(t, []string{"2", "4"}, stringSeq.Slice())
   415  ```
   416  
   417  `BreakLoop` and `BreakKVLoop` are used for sources that can issue an
   418  error.
   419  
   420  ``` go
   421  intSeq := loop.Conv(loop.Of("1", "2", "3", "ddd4", "5"), strconv.Atoi)
   422  ints, err := loop.Slice(intSeq)
   423  
   424  assert.Equal(t, []int{1, 2, 3}, ints)
   425  assert.ErrorContains(t, err, "invalid syntax")
   426  ```
   427  
   428  The API in most cases is similar to the [slice](./slice/api.go) API but
   429  with delayed computation which means that the methods don’t compute a
   430  result but only return a loop provider. The loop provider is type with a
   431  `Next` method that returns a next processed element.
   432  
   433  ### Main loop functions
   434  
   435  #### Instantiators
   436  
   437  ##### loop.Of, loop.S
   438  
   439  ``` go
   440  import "github.com/m4gshm/gollections/loop"
   441  
   442  var (
   443      ints    = loop.Of(1, 2, 3)
   444      strings = loop.S([]string{"a", "b", "c"})
   445  )
   446  ```
   447  
   448  ##### range\_.Of
   449  
   450  ``` go
   451  import "github.com/m4gshm/gollections/loop/range_"
   452  
   453  var increasing = range_.Of(-1, 3).Slice()    //[]int{-1, 0, 1, 2}
   454  var decreasing = range_.Of('e', 'a').Slice() //[]rune{'e', 'd', 'c', 'b'}
   455  var nothing = range_.Of(1, 1).Slice()        //nil
   456  ```
   457  
   458  ##### range\_.Closed
   459  
   460  ``` go
   461  var increasing = range_.Closed(-1, 3).Slice()    //[]int{-1, 0, 1, 2, 3}
   462  var decreasing = range_.Closed('e', 'a').Slice() //[]rune{'e', 'd', 'c', 'b', 'a'}
   463  var one = range_.Closed(1, 1).Slice()            //[]int{1}
   464  ```
   465  
   466  #### To map converters
   467  
   468  ##### group.Of
   469  
   470  ``` go
   471  import "github.com/m4gshm/gollections/convert/as"
   472  import "github.com/m4gshm/gollections/expr/use"
   473  import "github.com/m4gshm/gollections/loop/group"
   474  
   475  var ageGroups = group.Of(users, func(u User) string {
   476      return use.If(u.age <= 20, "<=20").If(u.age <= 30, "<=30").Else(">30")
   477  }, as.Is)
   478  
   479  //map[<=20:[{Tom 18 []}] <=30:[{Bob 26 []}] >30:[{Alice 35 []} {Chris 41 []}]]
   480  ```
   481  
   482  ##### loop.ToMap, loop.ToMapResolv
   483  
   484  ``` go
   485  import (
   486      "github.com/m4gshm/gollections/map_/resolv"
   487      "github.com/m4gshm/gollections/op"
   488      "github.com/m4gshm/gollections/loop"
   489  )
   490  
   491  var ageGroupedSortedNames map[string][]string
   492  
   493  ageGroupedSortedNames = loop.ToMapResolv(loop.Of(users...), func(u User) string {
   494      return op.IfElse(u.age <= 30, "<=30", ">30")
   495  }, User.Name, resolv.SortedSlice)
   496  
   497  //map[<=30:[Bob Tom] >30:[Alice Chris]]
   498  ```
   499  
   500  #### Reducers
   501  
   502  ##### sum.Of
   503  
   504  ``` go
   505  import "github.com/m4gshm/gollections/op/sum"
   506  
   507  var sum = sum.Of(loop.Of(1, 2, 3, 4, 5, 6)) //21
   508  ```
   509  
   510  ##### loop.Reduce
   511  
   512  ``` go
   513  var sum = loop.Reduce(loop.Of(1, 2, 3, 4, 5, 6), func(i1, i2 int) int { return i1 + i2 })
   514  //21
   515  ```
   516  
   517  ##### loop.First
   518  
   519  ``` go
   520  import "github.com/m4gshm/gollections/predicate/more"
   521  import "github.com/m4gshm/gollections/loop"
   522  
   523  result, ok := loop.First(loop.Of(1, 3, 5, 7, 9, 11), more.Than(5)) //7, true
   524  ```
   525  
   526  #### Converters
   527  
   528  ##### loop.Convert
   529  
   530  ``` go
   531  var s []string = loop.Convert(loop.Of(1, 3, 5, 7, 9, 11), strconv.Itoa).Slice()
   532  //[]string{"1", "3", "5", "7", "9", "11"}
   533  ```
   534  
   535  ##### loop.Conv
   536  
   537  ``` go
   538  result, err := loop.Conv(loop.Of("1", "3", "5", "_7", "9", "11"), strconv.Atoi).Slice()
   539  //[]int{1, 3, 5}, ErrSyntax
   540  ```
   541  
   542  ##### loop.Filter
   543  
   544  ``` go
   545  import "github.com/m4gshm/gollections/predicate/exclude"
   546  import "github.com/m4gshm/gollections/predicate/one"
   547  import "github.com/m4gshm/gollections/loop"
   548  
   549  var f1 = loop.Filter(loop.Of(1, 3, 5, 7, 9, 11), one.Of(1, 7).Or(one.Of(11))).Slice() //[]int{1, 7, 11}
   550  var f2 = loop.Filter(loop.Of(1, 3, 5, 7, 9, 11), exclude.All(1, 7, 11)).Slice()       //[]int{3, 5, 9}
   551  ```
   552  
   553  ##### loop.Flat
   554  
   555  ``` go
   556  import "github.com/m4gshm/gollections/convert/as"
   557  import "github.com/m4gshm/gollections/loop"
   558  
   559  var i []int = loop.Flat(loop.Of([][]int{{1, 2, 3}, {4}, {5, 6}}...), as.Is).Slice()
   560  //[]int{1, 2, 3, 4, 5, 6}
   561  ```
   562  
   563  #### Operations chain functions
   564  
   565  - convert.AndReduce, conv.AndReduce
   566  
   567  - convert.AndFilter
   568  
   569  - filter.AndConvert
   570  
   571  These functions combine converters, filters and reducers.
   572  
   573  ### Iterating over loops
   574  
   575  - (only for go 1.22) Using rangefunc `All` like:
   576  
   577  ``` go
   578  for i := range range_.Of(0, 100).All {
   579      doOp(i)
   580  }
   581  ```
   582  
   583  don’t forget exec `go env -w GOEXPERIMENT=rangefunc` before compile.
   584  
   585  - Using `for` statement like:
   586  
   587  ``` go
   588  next := range_.Of(0, 100)
   589  for i, ok := next(); ok; i, ok = next() {
   590      doOp(i)
   591  }
   592  ```
   593  
   594  - or
   595  
   596  ``` go
   597  for next, i, ok := range_.Of(0, 100).Crank(); ok; i, ok = next() {
   598      doOp(i)
   599  }
   600  ```
   601  
   602  - `ForEach` method
   603  
   604  ``` go
   605  range_.Of(0, 100).ForEach(doOp)
   606  ```
   607  
   608  - or `For` method that can be aborted by returning `Break` for expected
   609    completion, or another error otherwise.
   610  
   611  ``` go
   612  range_.Of(0, 100).For(func(i int) error {
   613      if i > 22 {
   614          return loop.Break
   615      }
   616      doOp(i)
   617      return loop.Continue
   618  })
   619  ```
   620  
   621  ## Data structures
   622  
   623  ### [mutable](./collection/mutable/api.go) and [immutable](./collection/immutable/api.go) collections
   624  
   625  Provides implelentations of [Vector](./collection/iface.go#L25),
   626  [Set](./collection/iface.go#L35) and [Map](./collection/iface.go#L41).
   627  
   628  Mutables support content appending, updating and deleting (the ordered
   629  map implementation is not supported delete operations).  
   630  Immutables are read-only datasets.
   631  
   632  Detailed description of implementations [below](#mutable-collections).
   633  
   634  ## Additional API
   635  
   636  ### [predicate](./predicate/api.go) and breakable [break/predicate](./predicate/api.go)
   637  
   638  Provides predicate builder api that used for filtering collection
   639  elements.
   640  
   641  ``` go
   642  import "github.com/m4gshm/gollections/predicate/where"
   643  import "github.com/m4gshm/gollections/slice"
   644  
   645  bob, _ := slice.First(users, where.Eq(User.Name, "Bob"))
   646  ```
   647  
   648  It is used for computations where an error may occur.
   649  
   650  ``` go
   651  intSeq := loop.Conv(loop.Of("1", "2", "3", "ddd4", "5"), strconv.Atoi)
   652  ints, err := loop.Slice(intSeq)
   653  
   654  assert.Equal(t, []int{1, 2, 3}, ints)
   655  assert.ErrorContains(t, err, "invalid syntax")
   656  ```
   657  
   658  ### Expressions: [use.If](./expr/use/api.go), [get.If](./expr/get/api.go), [first.Of](#firstof), [last.Of](#lastof)
   659  
   660  Aimed to evaluate a value using conditions. May cause to make code
   661  shorter by not in all cases.  
   662  As example:
   663  
   664  ``` go
   665  import "github.com/m4gshm/gollections/expr/use"
   666  
   667  user := User{name: "Bob", surname: "Smith"}
   668  
   669  fullName := use.If(len(user.surname) == 0, user.name).If(len(user.name) == 0, user.surname).
   670      ElseGet(func() string { return user.name + " " + user.surname })
   671  
   672  assert.Equal(t, "Bob Smith", fullName)
   673  ```
   674  
   675  instead of:
   676  
   677  ``` go
   678  user := User{name: "Bob", surname: "Smith"}
   679  
   680  fullName := ""
   681  if len(user.surname) == 0 {
   682      fullName = user.name
   683  } else if len(user.name) == 0 {
   684      fullName = user.surname
   685  } else {
   686      fullName = user.name + " " + user.surname
   687  }
   688  
   689  assert.Equal(t, "Bob Smith", fullName)
   690  ```
   691  
   692  #### first.Of
   693  
   694  ``` go
   695  import "github.com/m4gshm/gollections/expr/first"
   696  import "github.com/m4gshm/gollections/predicate/more"
   697  
   698  result, ok := first.Of(1, 3, 5, 7, 9, 11).By(more.Than(5)) //7, true
   699  ```
   700  
   701  #### last.Of
   702  
   703  ``` go
   704  import "github.com/m4gshm/gollections/expr/last"
   705  import "github.com/m4gshm/gollections/predicate/less"
   706  
   707  result, ok := last.Of(1, 3, 5, 7, 9, 11).By(less.Than(9)) //7, true
   708  ```
   709  
   710  ## Mutable collections
   711  
   712  Supports write operations (append, delete, replace).
   713  
   714  - [Vector](./collection/mutable/vector/api.go) - the simplest based on
   715    built-in slice collection.
   716  
   717  ``` go
   718  _ *mutable.Vector[int] = vector.Of(1, 2, 3)
   719  _ *mutable.Vector[int] = &mutable.Vector[int]{}
   720  ```
   721  
   722  - [Set](./collection/mutable/set/api.go) - collection of unique items,
   723    prevents duplicates.
   724  
   725  ``` go
   726  _ *mutable.Set[int] = set.Of(1, 2, 3)
   727  _ *mutable.Set[int] = &mutable.Set[int]{}
   728  ```
   729  
   730  - [Map](./collection/mutable/map_/api.go) - built-in map wrapper that
   731    supports [stream functions](#stream-functions).
   732  
   733  ``` go
   734  _ *mutable.Map[int, string] = map_.Of(k.V(1, "1"), k.V(2, "2"), k.V(3, "3"))
   735  _ *mutable.Map[int, string] = mutable.NewMapOf(map[int]string{1: "2", 2: "2", 3: "3"})
   736  ```
   737  
   738  - [OrderedSet](./collection/mutable/oset/api.go) - collection of unique
   739    items, prevents duplicates, provides iteration in order of addition.
   740  
   741  ``` go
   742  _ *ordered.Set[int] = set.Of(1, 2, 3)
   743  _ *ordered.Set[int] = &ordered.Set[int]{}
   744  ```
   745  
   746  - [OrderedMap](./collection/mutable/omap/api.go) - same as the Map, but
   747    supports iteration in the order in which elements are added.
   748  
   749  ``` go
   750  _ *ordered.Map[int, string] = map_.Of(k.V(1, "1"), k.V(2, "2"), k.V(3, "3"))
   751  _ *ordered.Map[int, string] = ordered.NewMapOf(
   752      /*order  */ []int{3, 1, 2},
   753      /*uniques*/ map[int]string{1: "2", 2: "2", 3: "3"},
   754  )
   755  ```
   756  
   757  ### Immutable collections
   758  
   759  The same underlying interfaces but for read-only use cases.
   760  
   761  ### Iterating over collections
   762  
   763  - (only for go 1.22) Using rangefunc `All` like:
   764  
   765  ``` go
   766  uniques := set.From(range_.Of(0, 100))
   767  for i := range uniques.All {
   768      doOp(i)
   769  }
   770  ```
   771  
   772  - Using `for` statement like:
   773  
   774  ``` go
   775  uniques := set.From(range_.Of(0, 100))
   776  next := uniques.Loop()
   777  for i, ok := next(); ok; i, ok = next() {
   778      doOp(i)
   779  }
   780  ```
   781  
   782  - or
   783  
   784  ``` go
   785  uniques := set.From(range_.Of(0, 100))
   786  for iter, i, ok := uniques.First(); ok; i, ok = iter.Next() {
   787      doOp(i)
   788  }
   789  ```
   790  
   791  - `ForEach` method
   792  
   793  ``` go
   794  uniques := set.From(range_.Of(0, 100))
   795  uniques.ForEach(doOp)
   796  ```
   797  
   798  - or `For` method that can be aborted by returning `Break` for expected
   799    completion, or another error otherwise.
   800  
   801  ``` go
   802  uniques := set.From(range_.Of(0, 100))
   803  uniques.For(func(i int) error {
   804      if i > 22 {
   805          return loop.Break
   806      }
   807      doOp(i)
   808      return loop.Continue
   809  })
   810  ```