Proposal Details

I propose the creation of two new function, Map and Filter, to the slices package:

Map

Map creates a new slice containing the results of running all indices of a slice through a provided functions

func Map[S ~[]E, E, R any](slice S, mapper func(int, E) R) []R

Example:

// Square all integers in a slice
slices.Map(
    []int{1, 2, 3, 4},
    func(i, v int) int {
        return v * v
    },
)
// Output: [1 4 9 16]

Filter

Filter create a new slice containing a subset of indices of an existing slice based on running all indices through a provided filter function

func Filter[S ~[]E, E any](slice S, filter func(int, E) bool) []E

Example:

// Filter out all odd numbers in a slice
slices.Filter(
    []int{1, 2, 3, 4},
    func(i, v int) bool {
        return v%2 == 0
    },
)
// Output: [2 4]

Reasoning

Personally I find myself creating these exact function in most of my projects and they help a ton with writing clean code.

It allows me to change from code like this:

existingSlice := []int{1, 2, 3, 4}

filterSlice := make([]int, 0, len(existingSlice))
for _, v := range existingSlice {
    if v%2 == 0 {
        filterSlice = append(filterSlice, v)
    }
}

mappedSlice := make([]string, len(filterSlice))
for i, v := range filterSlice {
    mappedSlice[i] = strconv.Itoa(v)
}

and simplify it something like this:

existingSlice := []int{1, 2, 3, 4}

filterSlice := slice.Filter(existingSlice, func(i, v int) bool { return v%2 == 0 })
mappedSlice := slice.Map(filterSlice, func(i, v int) string { return strconv.Itoa(v) })

** Also note: this is super useful for doing inline operation

Implementation

New time contributor here, so I did not know that proposals were needed before making API change. I already have proposed implementation using the patterns that the slices packages uses:

// Map returns a new slice containing the result of applying the mapper function to each element of the provided.
// The result has the same length as the input slice.
// The result is never nil.
// The result will be in the same order as the input slice.
// The mapper function must not modify the elements of the input slice.
func Map[S ~[]E, E, R any](slice S, mapper func(int, E) R) []R {
    mappedSlice := make([]R, len(slice))
    for i, v := range slice {
        mappedSlice[i] = mapper(i, v)
    }
    return mappedSlice
}

// Filter returns a new slice containing only the elements of the provided slice for which the filter function returns true.
// The result has length <= len(slice).
// The result is never nil.
// The result will be in the same order as the input slice.
// The filter function must not modify the elements of the input slice.
func Filter[S ~[]E, E any](slice S, filter func(int, E) bool) []E {
    filteredSlice := make([]E, 0, len(slice))
    for i, v := range slice {
        if filter(i, v) {
            filteredSlice = append(filteredSlice, v)
        }
    }
    return Clip(filteredSlice)
}

Comment From: gabyhelp

Similar Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: 1nv8rzim

Reading through the similar issues, all of the similar proposals have been closed and they seem to point to https://github.com/golang/go/discussions/47203#discussioncomment-1034432 as the reason to why.

  • Deleted Map, Filter, Reduce (probably better as part of a more comprehensive streams API somewhere else)

Reasoning to why these haven't been implemented is in anticipation of this "streams API" which I would assume would refer to the iter package: https://github.com/golang/go/issues/61899. Likewise, slices seems to be getting these iterator functions as recent as a month ago: https://go-review.googlesource.com/c/go/+/568477.

However, it seems as though functions like Map and Filter might have fallen through the cracks?

Comment From: ianlancetaylor

This is #61898. Thanks.