Repository: elliotchance/pie
Pull request #120 (2019-05-09 06:52:17)

added: Strings
This also allows fmt.Stringer to be used.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/elliotchance/pie/120)
<!-- Reviewable:end -->


codecov-io: # [Codecov](https://codecov.io/gh/elliotchance/pie/pull/120?src=pr&el=h1) Report
> :exclamation: No coverage uploaded for pull request base (`master@81bd720`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference#section-missing-base-commit).
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/elliotchance/pie/pull/120/graphs/tree.svg?width=650&token=OvugwUQGM3&height=150&src=pr)](https://codecov.io/gh/elliotchance/pie/pull/120?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##             master   #120   +/-   ##
=======================================
  Coverage          ?   100%           
=======================================
  Files             ?      8           
  Lines             ?   1104           
  Branches          ?      0           
=======================================
  Hits              ?   1104           
  Misses            ?      0           
  Partials          ?      0
```


| [Impacted Files](https://codecov.io/gh/elliotchance/pie/pull/120?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pie/float64s\_pie.go](https://codecov.io/gh/elliotchance/pie/pull/120/diff?src=pr&el=tree#diff-cGllL2Zsb2F0NjRzX3BpZS5nbw==) | `100% <100%> (ø)` | |
| [pie/car.go](https://codecov.io/gh/elliotchance/pie/pull/120/diff?src=pr&el=tree#diff-cGllL2Nhci5nbw==) | `100% <100%> (ø)` | |
| [pie/carpointers\_pie.go](https://codecov.io/gh/elliotchance/pie/pull/120/diff?src=pr&el=tree#diff-cGllL2NhcnBvaW50ZXJzX3BpZS5nbw==) | `100% <100%> (ø)` | |
| [pie/cars\_pie.go](https://codecov.io/gh/elliotchance/pie/pull/120/diff?src=pr&el=tree#diff-cGllL2NhcnNfcGllLmdv) | `100% <100%> (ø)` | |
| [pie/strings\_pie.go](https://codecov.io/gh/elliotchance/pie/pull/120/diff?src=pr&el=tree#diff-cGllL3N0cmluZ3NfcGllLmdv) | `100% <100%> (ø)` | |
| [pie/ints\_pie.go](https://codecov.io/gh/elliotchance/pie/pull/120/diff?src=pr&el=tree#diff-cGllL2ludHNfcGllLmdv) | `100% <100%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/elliotchance/pie/pull/120?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/elliotchance/pie/pull/120?src=pr&el=footer). Last update [81bd720...1053106](https://codecov.io/gh/elliotchance/pie/pull/120?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index b51e135..e5c68d5 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ focuses on type safety, performance and immutability.
   * [Built-in Types](#built-in-types)
   * [Custom Types](#custom-types)
   * [Custom Equality](#custom-equality)
+  * [Custom Stringer](#custom-stringer)
   * [Limiting Functions Generated](#limiting-functions-generated)
 - [Functions](#functions)
 - [FAQ](#faq)
@@ -136,6 +137,16 @@ func (c *Car) Equals(c2 *Car) bool {
 }
 ```
 
+## Custom Stringer
+
+Some functions that need elements to be represented as strings, such as
+`Strings()` will try to use the `fmt.Stringer` interface. If it's not available
+then it will fallback to:
+
+```go
+fmt.Sprintf("%v", element)
+```
+
 ## Limiting Functions Generated
 
 The `.*` can be used to generate all functions. This is easy to get going but
@@ -152,8 +163,13 @@ This will only generate `myInts.Average`, `myInts.Sum` and `myStrings.Filter`.
 
 Below is a summary of the available functions.
 
-The "(E)" after the function name indicates that the function will use the
-`Equals` method if it is available. See *Custom Equality*.
+The letters in brackets indicate:
+
+- **E**: The function will use the `Equals` method if it is available. See
+*Custom Equality*.
+
+- **S**: The function will use the `String` method if it is available. See
+*Custom Stringer*.
 
 | Function          | String | Number | Struct | Maps | Big-O    | Description |
 | ----------------- | :----: | :----: | :----: | :--: | :------: | ----------- |
@@ -167,8 +183,8 @@ The "(E)" after the function name indicates that the function will use the
 | `Bottom`          | ✓      | ✓      | ✓      |      | n        | Gets n elements from bottom. |
 | `Contains` (E)    | ✓      | ✓      | ✓      |      | n        | Check if the value exists in the slice. |
 | `Diff` (E)        | ✓      | ✓      | ✓      |      | n²       | Diff returns the elements that needs to be added or removed from the first slice to have the same elements in the second slice. |
-| `Extend`          | ✓      | ✓      | ✓      |      | n        | A new slice with the elements from each slice appended to the end. |
 | `Each`            | ✓      | ✓      | ✓      |      | n        | Perform an action on each element. |
+| `Extend`          | ✓      | ✓      | ✓      |      | n        | A new slice with the elements from each slice appended to the end. |
 | `Filter`          | ✓      | ✓      | ✓      |      | n        | A new slice containing only the elements that returned true from the condition. |
 | `FilterNot`       | ✓      | ✓      | ✓      |      | n        | A new slice containing only the elements that returned false from the condition. |
 | `First`           | ✓      | ✓      | ✓      |      | 1        | The first element, or a zeroed value. |
@@ -189,12 +205,13 @@ The "(E)" after the function name indicates that the function will use the
 | `Reduce`          | ✓      | ✓      |        |      | n        | Continously apply a function to the slice (left to right), reducing it to a single value. |
 | `Reverse`         | ✓      | ✓      | ✓      |      | n        | Reverse elements. |
 | `Send`            | ✓      | ✓      | ✓      |      | n        | Send all element to channel. |
-| `Sequence`     | ✓      | ✓      |    |      | n        | Generates sequence of numbers. |
+| `Sequence`        | ✓      | ✓      |        |      | n        | Generates sequence of numbers. |
+| `Shuffle`         | ✓      | ✓      | ✓      |      | n        | Returns a new shuffled slice. |
 | `Sort`            | ✓      | ✓      |        |      | n⋅log(n) | Return a new sorted slice. |
-| `SortUsing`       | ✓      |        | ✓      |      | n⋅log(n) | Return a new sorted slice, using custom comparator. |
 | `SortStableUsing` | ✓      |        | ✓      |      | n⋅log(n) | Return a new sorted slice, using custom comparator, keeping the original order of equal elements. |
+| `SortUsing`       | ✓      |        | ✓      |      | n⋅log(n) | Return a new sorted slice, using custom comparator. |
+| `Strings` (S)     | ✓      | ✓      | ✓      |      | n        | Transforms each element into a string. |
 | `Sum`             |        | ✓      |        |      | n        | Sum (total) of all elements. |
-| `Shuffle`         | ✓      | ✓      | ✓      |      | n        | Returns a new shuffled slice. |
 | `Top`             | ✓      | ✓      | ✓      |      | n        | Gets several elements from top(head of slice).|
 | `ToStrings`       | ✓      | ✓      | ✓      |      | n        | Transforms each element to a string. |
 | `Unique`          | ✓      | ✓      |        |      | n        | Return a new slice with only unique elements. |
diff --git a/functions/main.go b/functions/main.go
index 6946d11..ce09153 100644
--- a/functions/main.go
+++ b/functions/main.go
@@ -1,5 +1,7 @@
 package functions
 
+import "fmt"
+
 const (
 	ForNumbers = 1 << iota
 	ForStrings
@@ -51,11 +53,12 @@ var Functions = []struct {
 	{"Reverse", "reverse.go", ForAll},
 	{"Send", "send.go", ForAll},
 	{"Sequence", "sequence.go", ForNumbers},
+	{"Shuffle", "shuffle.go", ForAll},
 	{"Sort", "sort.go", ForNumbersAndStrings},
-	{"SortUsing", "sort_using.go", ForStrings | ForStructs},
 	{"SortStableUsing", "sort_stable_using.go", ForStrings | ForStructs},
+	{"SortUsing", "sort_using.go", ForStrings | ForStructs},
+	{"Strings", "strings.go", ForAll},
 	{"Sum", "sum.go", ForNumbers},
-	{"Shuffle", "shuffle.go", ForAll},
 	{"Top", "top.go", ForAll},
 	{"ToStrings", "to_strings.go", ForAll},
 	{"Unique", "unique.go", ForNumbersAndStrings},
@@ -75,3 +78,7 @@ var ElementZeroValue ElementType
 func (a ElementType) Equals(b ElementType) bool {
 	return a == b
 }
+
+func (a ElementType) String() string {
+	return fmt.Sprintf("%f", a)
+}
diff --git a/functions/strings.go b/functions/strings.go
new file mode 100644
index 0000000..ce2a058
--- /dev/null
+++ b/functions/strings.go
@@ -0,0 +1,29 @@
+package functions
+
+import (
+	"github.com/elliotchance/pie/pie"
+)
+
+// Strings transforms each element to a string.
+//
+// If the element type implements fmt.Stringer it will be used. Otherwise it
+// will fallback to the result of:
+//
+//   fmt.Sprintf("%v")
+//
+func (ss SliceType) Strings() pie.Strings {
+	l := len(ss)
+
+	// Avoid the allocation.
+	if l == 0 {
+		return nil
+	}
+
+	result := make(pie.Strings, l)
+	for i := 0; i < l; i++ {
+		mightBeString := ss[i]
+		result[i] = mightBeString.String()
+	}
+
+	return result
+}
diff --git a/main.go b/main.go
index ace61b1..e3769f9 100644
--- a/main.go
+++ b/main.go
@@ -34,27 +34,25 @@ func getIdentName(e ast.Expr) string {
 	}
 }
 
-func getKeyAndElementType(pkg *ast.Package, name string, typeSpec *ast.TypeSpec) (string, string, string, bool) {
+func getKeyAndElementType(pkg *ast.Package, name string, typeSpec *ast.TypeSpec) (string, string, string, *TypeExplorer) {
 	pkgName := pkg.Name
 
 	if t, ok := typeSpec.Type.(*ast.ArrayType); ok {
 		explorer := NewTypeExplorer(pkg, getIdentName(t.Elt))
-		hasEquals := explorer.HasEquals()
 
-		return pkgName, "", getIdentName(t.Elt), hasEquals
+		return pkgName, "", getIdentName(t.Elt), explorer
 	}
 
 	if t, ok := typeSpec.Type.(*ast.MapType); ok {
 		explorer := NewTypeExplorer(pkg, getIdentName(t.Value))
-		hasEquals := explorer.HasEquals()
 
-		return pkgName, getIdentName(t.Key), getIdentName(t.Value), hasEquals
+		return pkgName, getIdentName(t.Key), getIdentName(t.Value), explorer
 	}
 
 	panic(fmt.Sprintf("type %s must be a slice or map", name))
 }
 
-func findType(pkgs map[string]*ast.Package, name string) (packageName, keyType, elementType string, hasEquals bool) {
+func findType(pkgs map[string]*ast.Package, name string) (packageName, keyType, elementType string, explorer *TypeExplorer) {
 	for _, pkg := range pkgs {
 		for _, file := range pkg.Files {
 			for _, decl := range file.Decls {
@@ -113,10 +111,14 @@ func getImports(packageName, s string) (imports []string) {
 	return
 }
 
-func getAllImports(packageName string, files []string) (imports []string) {
+func getAllImports(packageName string, files []string, explorer *TypeExplorer) (imports []string) {
 	mapImports := map[string]struct{}{}
 
 	for _, file := range files {
+		if !explorer.HasString() && strings.Contains(file, "mightBeString") {
+			mapImports[`"fmt"`] = struct{}{}
+		}
+
 		for _, imp := range getImports(packageName, file) {
 			mapImports[imp] = struct{}{}
 		}
@@ -144,7 +146,7 @@ func main() {
 
 	for _, arg := range os.Args[1:] {
 		mapOrSliceType, fns := getFunctionsFromArg(arg)
-		packageName, keyType, elementType, hasEquals := findType(pkgs, mapOrSliceType)
+		packageName, keyType, elementType, explorer := findType(pkgs, mapOrSliceType)
 		kind := getType(keyType, elementType)
 
 		var templates []string
@@ -161,7 +163,7 @@ func main() {
 		// Aggregate imports.
 		t := fmt.Sprintf("package %s\n\n", packageName)
 
-		imports := getAllImports(packageName, templates)
+		imports := getAllImports(packageName, templates, explorer)
 		if len(imports) > 0 {
 			t += fmt.Sprintf("import (")
 			for _, imp := range imports {
@@ -183,13 +185,17 @@ func main() {
 		t = strings.Replace(t, "KeySliceType", "[]"+keyType, -1)
 		t = strings.Replace(t, "SliceType", mapOrSliceType, -1)
 
-		if !hasEquals {
+		if !explorer.HasEquals() {
 			re := regexp.MustCompile(`([\w_]+)\.Equals\(([^)]+)\)`)
 			t = ReplaceAllStringSubmatchFunc(re, t, func(groups []string) string {
 				return fmt.Sprintf("%s == %s", groups[1], groups[2])
 			})
 		}
 
+		if !explorer.HasString() {
+			t = strings.Replace(t, "mightBeString.String()", `fmt.Sprintf("%v", mightBeString)`, -1)
+		}
+
 		switch kind {
 		case functions.ForNumbers:
 			t = strings.Replace(t, "ElementZeroValue", "0", -1)
diff --git a/pie/car.go b/pie/car.go
index 8d54992..a2f1c48 100644
--- a/pie/car.go
+++ b/pie/car.go
@@ -1,5 +1,7 @@
 package pie
 
+import "fmt"
+
 //go:generate pie cars.* carPointers.*
 type cars []car
 type carPointers []*car
@@ -19,3 +21,7 @@ func (c *car) Equals(c2 *car) bool {
 
 	return c.Name == c2.Name
 }
+
+func (c *car) String() string {
+	return fmt.Sprintf("%s is %s", c.Name, c.Color)
+}
diff --git a/pie/carpointers_pie.go b/pie/carpointers_pie.go
index 3a94d08..a406ee1 100755
--- a/pie/carpointers_pie.go
+++ b/pie/carpointers_pie.go
@@ -3,6 +3,7 @@ package pie
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"github.com/elliotchance/pie/pie/util"
 	"math/rand"
 	"sort"
@@ -303,9 +304,33 @@ func (ss carPointers) Send(ctx context.Context, ch chan<- *car) carPointers {
 	return ss
 }
 
-// SortUsing works similar to sort.Slice. However, unlike sort.Slice the
+// Shuffle returns shuffled slice by your rand.Source
+func (ss carPointers) Shuffle(source rand.Source) carPointers {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 2 {
+		return ss
+	}
+
+	// go 1.10+ provides rnd.Shuffle. However, to support older versions we copy
+	// the algorithm directly from the go source: src/math/rand/rand.go below,
+	// with some adjustments:
+	shuffled := make([]*car, n)
+	copy(shuffled, ss)
+
+	rnd := rand.New(source)
+
+	util.Shuffle(rnd, n, func(i, j int) {
+		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
+	})
+
+	return shuffled
+}
+
+// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the
 // slice returned will be reallocated as to not modify the input slice.
-func (ss carPointers) SortUsing(less func(a, b *car) bool) carPointers {
+func (ss carPointers) SortStableUsing(less func(a, b *car) bool) carPointers {
 	// Avoid the allocation. If there is one element or less it is already
 	// sorted.
 	if len(ss) < 2 {
@@ -314,16 +339,16 @@ func (ss carPointers) SortUsing(less func(a, b *car) bool) carPointers {
 
 	sorted := make(carPointers, len(ss))
 	copy(sorted, ss)
-	sort.Slice(sorted, func(i, j int) bool {
+	sort.SliceStable(sorted, func(i, j int) bool {
 		return less(sorted[i], sorted[j])
 	})
 
 	return sorted
 }
 
-// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the
+// SortUsing works similar to sort.Slice. However, unlike sort.Slice the
 // slice returned will be reallocated as to not modify the input slice.
-func (ss carPointers) SortStableUsing(less func(a, b *car) bool) carPointers {
+func (ss carPointers) SortUsing(less func(a, b *car) bool) carPointers {
 	// Avoid the allocation. If there is one element or less it is already
 	// sorted.
 	if len(ss) < 2 {
@@ -332,35 +357,35 @@ func (ss carPointers) SortStableUsing(less func(a, b *car) bool) carPointers {
 
 	sorted := make(carPointers, len(ss))
 	copy(sorted, ss)
-	sort.SliceStable(sorted, func(i, j int) bool {
+	sort.Slice(sorted, func(i, j int) bool {
 		return less(sorted[i], sorted[j])
 	})
 
 	return sorted
 }
 
-// Shuffle returns shuffled slice by your rand.Source
-func (ss carPointers) Shuffle(source rand.Source) carPointers {
-	n := len(ss)
+// Strings transforms each element to a string.
+//
+// If the element type implements fmt.Stringer it will be used. Otherwise it
+// will fallback to the result of:
+//
+//   fmt.Sprintf("%v")
+//
+func (ss carPointers) Strings() Strings {
+	l := len(ss)
 
-	// Avoid the extra allocation.
-	if n < 2 {
-		return ss
+	// Avoid the allocation.
+	if l == 0 {
+		return nil
 	}
 
-	// go 1.10+ provides rnd.Shuffle. However, to support older versions we copy
-	// the algorithm directly from the go source: src/math/rand/rand.go below,
-	// with some adjustments:
-	shuffled := make([]*car, n)
-	copy(shuffled, ss)
-
-	rnd := rand.New(source)
-
-	util.Shuffle(rnd, n, func(i, j int) {
-		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
-	})
+	result := make(Strings, l)
+	for i := 0; i < l; i++ {
+		mightBeString := ss[i]
+		result[i] = fmt.Sprintf("%v", mightBeString)
+	}
 
-	return shuffled
+	return result
 }
 
 // Top will return n elements from head of the slice
diff --git a/pie/cars_pie.go b/pie/cars_pie.go
index 9378b06..5701142 100755
--- a/pie/cars_pie.go
+++ b/pie/cars_pie.go
@@ -3,6 +3,7 @@ package pie
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"github.com/elliotchance/pie/pie/util"
 	"math/rand"
 	"sort"
@@ -303,9 +304,33 @@ func (ss cars) Send(ctx context.Context, ch chan<- car) cars {
 	return ss
 }
 
-// SortUsing works similar to sort.Slice. However, unlike sort.Slice the
+// Shuffle returns shuffled slice by your rand.Source
+func (ss cars) Shuffle(source rand.Source) cars {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 2 {
+		return ss
+	}
+
+	// go 1.10+ provides rnd.Shuffle. However, to support older versions we copy
+	// the algorithm directly from the go source: src/math/rand/rand.go below,
+	// with some adjustments:
+	shuffled := make([]car, n)
+	copy(shuffled, ss)
+
+	rnd := rand.New(source)
+
+	util.Shuffle(rnd, n, func(i, j int) {
+		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
+	})
+
+	return shuffled
+}
+
+// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the
 // slice returned will be reallocated as to not modify the input slice.
-func (ss cars) SortUsing(less func(a, b car) bool) cars {
+func (ss cars) SortStableUsing(less func(a, b car) bool) cars {
 	// Avoid the allocation. If there is one element or less it is already
 	// sorted.
 	if len(ss) < 2 {
@@ -314,16 +339,16 @@ func (ss cars) SortUsing(less func(a, b car) bool) cars {
 
 	sorted := make(cars, len(ss))
 	copy(sorted, ss)
-	sort.Slice(sorted, func(i, j int) bool {
+	sort.SliceStable(sorted, func(i, j int) bool {
 		return less(sorted[i], sorted[j])
 	})
 
 	return sorted
 }
 
-// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the
+// SortUsing works similar to sort.Slice. However, unlike sort.Slice the
 // slice returned will be reallocated as to not modify the input slice.
-func (ss cars) SortStableUsing(less func(a, b car) bool) cars {
+func (ss cars) SortUsing(less func(a, b car) bool) cars {
 	// Avoid the allocation. If there is one element or less it is already
 	// sorted.
 	if len(ss) < 2 {
@@ -332,35 +357,35 @@ func (ss cars) SortStableUsing(less func(a, b car) bool) cars {
 
 	sorted := make(cars, len(ss))
 	copy(sorted, ss)
-	sort.SliceStable(sorted, func(i, j int) bool {
+	sort.Slice(sorted, func(i, j int) bool {
 		return less(sorted[i], sorted[j])
 	})
 
 	return sorted
 }
 
-// Shuffle returns shuffled slice by your rand.Source
-func (ss cars) Shuffle(source rand.Source) cars {
-	n := len(ss)
+// Strings transforms each element to a string.
+//
+// If the element type implements fmt.Stringer it will be used. Otherwise it
+// will fallback to the result of:
+//
+//   fmt.Sprintf("%v")
+//
+func (ss cars) Strings() Strings {
+	l := len(ss)
 
-	// Avoid the extra allocation.
-	if n < 2 {
-		return ss
+	// Avoid the allocation.
+	if l == 0 {
+		return nil
 	}
 
-	// go 1.10+ provides rnd.Shuffle. However, to support older versions we copy
-	// the algorithm directly from the go source: src/math/rand/rand.go below,
-	// with some adjustments:
-	shuffled := make([]car, n)
-	copy(shuffled, ss)
-
-	rnd := rand.New(source)
-
-	util.Shuffle(rnd, n, func(i, j int) {
-		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
-	})
+	result := make(Strings, l)
+	for i := 0; i < l; i++ {
+		mightBeString := ss[i]
+		result[i] = fmt.Sprintf("%v", mightBeString)
+	}
 
-	return shuffled
+	return result
 }
 
 // Top will return n elements from head of the slice
diff --git a/pie/float64s_pie.go b/pie/float64s_pie.go
index 838bf1c..ec268b5 100755
--- a/pie/float64s_pie.go
+++ b/pie/float64s_pie.go
@@ -3,6 +3,7 @@ package pie
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"github.com/elliotchance/pie/pie/util"
 	"math"
 	"math/rand"
@@ -496,6 +497,30 @@ func (ss Float64s) Sequence(params ...int) Float64s {
 	}
 }
 
+// Shuffle returns shuffled slice by your rand.Source
+func (ss Float64s) Shuffle(source rand.Source) Float64s {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 2 {
+		return ss
+	}
+
+	// go 1.10+ provides rnd.Shuffle. However, to support older versions we copy
+	// the algorithm directly from the go source: src/math/rand/rand.go below,
+	// with some adjustments:
+	shuffled := make([]float64, n)
+	copy(shuffled, ss)
+
+	rnd := rand.New(source)
+
+	util.Shuffle(rnd, n, func(i, j int) {
+		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
+	})
+
+	return shuffled
+}
+
 // Sort works similar to sort.Float64s(). However, unlike sort.Float64s the
 // slice returned will be reallocated as to not modify the input slice.
 //
@@ -516,6 +541,30 @@ func (ss Float64s) Sort() Float64s {
 	return sorted
 }
 
+// Strings transforms each element to a string.
+//
+// If the element type implements fmt.Stringer it will be used. Otherwise it
+// will fallback to the result of:
+//
+//   fmt.Sprintf("%v")
+//
+func (ss Float64s) Strings() Strings {
+	l := len(ss)
+
+	// Avoid the allocation.
+	if l == 0 {
+		return nil
+	}
+
+	result := make(Strings, l)
+	for i := 0; i < l; i++ {
+		mightBeString := ss[i]
+		result[i] = fmt.Sprintf("%v", mightBeString)
+	}
+
+	return result
+}
+
 // Sum is the sum of all of the elements.
 func (ss Float64s) Sum() (sum float64) {
 	for _, s := range ss {
@@ -525,30 +574,6 @@ func (ss Float64s) Sum() (sum float64) {
 	return
 }
 
-// Shuffle returns shuffled slice by your rand.Source
-func (ss Float64s) Shuffle(source rand.Source) Float64s {
-	n := len(ss)
-
-	// Avoid the extra allocation.
-	if n < 2 {
-		return ss
-	}
-
-	// go 1.10+ provides rnd.Shuffle. However, to support older versions we copy
-	// the algorithm directly from the go source: src/math/rand/rand.go below,
-	// with some adjustments:
-	shuffled := make([]float64, n)
-	copy(shuffled, ss)
-
-	rnd := rand.New(source)
-
-	util.Shuffle(rnd, n, func(i, j int) {
-		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
-	})
-
-	return shuffled
-}
-
 // Top will return n elements from head of the slice
 // if the slice has less elements then n that'll return all elements
 // if n < 0 it'll return empty slice.
diff --git a/pie/ints_pie.go b/pie/ints_pie.go
index a2c4e1d..f503c92 100755
--- a/pie/ints_pie.go
+++ b/pie/ints_pie.go
@@ -3,6 +3,7 @@ package pie
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"github.com/elliotchance/pie/pie/util"
 	"math"
 	"math/rand"
@@ -496,6 +497,30 @@ func (ss Ints) Sequence(params ...int) Ints {
 	}
 }
 
+// Shuffle returns shuffled slice by your rand.Source
+func (ss Ints) Shuffle(source rand.Source) Ints {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 2 {
+		return ss
+	}
+
+	// go 1.10+ provides rnd.Shuffle. However, to support older versions we copy
+	// the algorithm directly from the go source: src/math/rand/rand.go below,
+	// with some adjustments:
+	shuffled := make([]int, n)
+	copy(shuffled, ss)
+
+	rnd := rand.New(source)
+
+	util.Shuffle(rnd, n, func(i, j int) {
+		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
+	})
+
+	return shuffled
+}
+
 // Sort works similar to sort.Ints(). However, unlike sort.Ints the
 // slice returned will be reallocated as to not modify the input slice.
 //
@@ -516,6 +541,30 @@ func (ss Ints) Sort() Ints {
 	return sorted
 }
 
+// Strings transforms each element to a string.
+//
+// If the element type implements fmt.Stringer it will be used. Otherwise it
+// will fallback to the result of:
+//
+//   fmt.Sprintf("%v")
+//
+func (ss Ints) Strings() Strings {
+	l := len(ss)
+
+	// Avoid the allocation.
+	if l == 0 {
+		return nil
+	}
+
+	result := make(Strings, l)
+	for i := 0; i < l; i++ {
+		mightBeString := ss[i]
+		result[i] = fmt.Sprintf("%v", mightBeString)
+	}
+
+	return result
+}
+
 // Sum is the sum of all of the elements.
 func (ss Ints) Sum() (sum int) {
 	for _, s := range ss {
@@ -525,30 +574,6 @@ func (ss Ints) Sum() (sum int) {
 	return
 }
 
-// Shuffle returns shuffled slice by your rand.Source
-func (ss Ints) Shuffle(source rand.Source) Ints {
-	n := len(ss)
-
-	// Avoid the extra allocation.
-	if n < 2 {
-		return ss
-	}
-
-	// go 1.10+ provides rnd.Shuffle. However, to support older versions we copy
-	// the algorithm directly from the go source: src/math/rand/rand.go below,
-	// with some adjustments:
-	shuffled := make([]int, n)
-	copy(shuffled, ss)
-
-	rnd := rand.New(source)
-
-	util.Shuffle(rnd, n, func(i, j int) {
-		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
-	})
-
-	return shuffled
-}
-
 // Top will return n elements from head of the slice
 // if the slice has less elements then n that'll return all elements
 // if n < 0 it'll return empty slice.
diff --git a/pie/strings_pie.go b/pie/strings_pie.go
index 8e90e49..fafd277 100755
--- a/pie/strings_pie.go
+++ b/pie/strings_pie.go
@@ -3,6 +3,7 @@ package pie
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"github.com/elliotchance/pie/pie/util"
 	"math/rand"
 	"sort"
@@ -413,6 +414,30 @@ func (ss Strings) Send(ctx context.Context, ch chan<- string) Strings {
 	return ss
 }
 
+// Shuffle returns shuffled slice by your rand.Source
+func (ss Strings) Shuffle(source rand.Source) Strings {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 2 {
+		return ss
+	}
+
+	// go 1.10+ provides rnd.Shuffle. However, to support older versions we copy
+	// the algorithm directly from the go source: src/math/rand/rand.go below,
+	// with some adjustments:
+	shuffled := make([]string, n)
+	copy(shuffled, ss)
+
+	rnd := rand.New(source)
+
+	util.Shuffle(rnd, n, func(i, j int) {
+		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
+	})
+
+	return shuffled
+}
+
 // Sort works similar to sort.Strings(). However, unlike sort.Strings the
 // slice returned will be reallocated as to not modify the input slice.
 //
@@ -433,9 +458,9 @@ func (ss Strings) Sort() Strings {
 	return sorted
 }
 
-// SortUsing works similar to sort.Slice. However, unlike sort.Slice the
+// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the
 // slice returned will be reallocated as to not modify the input slice.
-func (ss Strings) SortUsing(less func(a, b string) bool) Strings {
+func (ss Strings) SortStableUsing(less func(a, b string) bool) Strings {
 	// Avoid the allocation. If there is one element or less it is already
 	// sorted.
 	if len(ss) < 2 {
@@ -444,16 +469,16 @@ func (ss Strings) SortUsing(less func(a, b string) bool) Strings {
 
 	sorted := make(Strings, len(ss))
 	copy(sorted, ss)
-	sort.Slice(sorted, func(i, j int) bool {
+	sort.SliceStable(sorted, func(i, j int) bool {
 		return less(sorted[i], sorted[j])
 	})
 
 	return sorted
 }
 
-// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the
+// SortUsing works similar to sort.Slice. However, unlike sort.Slice the
 // slice returned will be reallocated as to not modify the input slice.
-func (ss Strings) SortStableUsing(less func(a, b string) bool) Strings {
+func (ss Strings) SortUsing(less func(a, b string) bool) Strings {
 	// Avoid the allocation. If there is one element or less it is already
 	// sorted.
 	if len(ss) < 2 {
@@ -462,35 +487,35 @@ func (ss Strings) SortStableUsing(less func(a, b string) bool) Strings {
 
 	sorted := make(Strings, len(ss))
 	copy(sorted, ss)
-	sort.SliceStable(sorted, func(i, j int) bool {
+	sort.Slice(sorted, func(i, j int) bool {
 		return less(sorted[i], sorted[j])
 	})
 
 	return sorted
 }
 
-// Shuffle returns shuffled slice by your rand.Source
-func (ss Strings) Shuffle(source rand.Source) Strings {
-	n := len(ss)
+// Strings transforms each element to a string.
+//
+// If the element type implements fmt.Stringer it will be used. Otherwise it
+// will fallback to the result of:
+//
+//   fmt.Sprintf("%v")
+//
+func (ss Strings) Strings() Strings {
+	l := len(ss)
 
-	// Avoid the extra allocation.
-	if n < 2 {
-		return ss
+	// Avoid the allocation.
+	if l == 0 {
+		return nil
 	}
 
-	// go 1.10+ provides rnd.Shuffle. However, to support older versions we copy
-	// the algorithm directly from the go source: src/math/rand/rand.go below,
-	// with some adjustments:
-	shuffled := make([]string, n)
-	copy(shuffled, ss)
-
-	rnd := rand.New(source)
-
-	util.Shuffle(rnd, n, func(i, j int) {
-		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
-	})
+	result := make(Strings, l)
+	for i := 0; i < l; i++ {
+		mightBeString := ss[i]
+		result[i] = fmt.Sprintf("%v", mightBeString)
+	}
 
-	return shuffled
+	return result
 }
 
 // Top will return n elements from head of the slice
diff --git a/template.go b/template.go
index 6211f49..e3bec15 100644
--- a/template.go
+++ b/template.go
@@ -714,6 +714,36 @@ func (ss SliceType) SortUsing(less func(a, b ElementType) bool) SliceType {
 
 	return sorted
 }
+`,
+	"Strings": `package functions
+
+import (
+	"github.com/elliotchance/pie/pie"
+)
+
+// Strings transforms each element to a string.
+//
+// If the element type implements fmt.Stringer it will be used. Otherwise it
+// will fallback to the result of:
+//
+//   fmt.Sprintf("%v")
+//
+func (ss SliceType) Strings() pie.Strings {
+	l := len(ss)
+
+	// Avoid the allocation.
+	if l == 0 {
+		return nil
+	}
+
+	result := make(pie.Strings, l)
+	for i := 0; i < l; i++ {
+		mightBeString := ss[i]
+		result[i] = mightBeString.String()
+	}
+
+	return result
+}
 `,
 	"Sum": `package functions
 
diff --git a/type_explorer.go b/type_explorer.go
index fca3158..064989e 100644
--- a/type_explorer.go
+++ b/type_explorer.go
@@ -33,7 +33,14 @@ func (explorer *TypeExplorer) Visit(node ast.Node) ast.Visitor {
 }
 
 func (explorer *TypeExplorer) HasEquals() bool {
-	lookingFor := fmt.Sprintf("Equals(%s)", explorer.TypeName)
+	return explorer.HasMethod(fmt.Sprintf("Equals(%s)", explorer.TypeName))
+}
+
+func (explorer *TypeExplorer) HasString() bool {
+	return explorer.HasMethod("String()")
+}
+
+func (explorer *TypeExplorer) HasMethod(lookingFor string) bool {
 	for _, method := range explorer.Methods {
 		if method == lookingFor {
 			return true


Test patch:
diff --git a/pie/carpointers_test.go b/pie/carpointers_test.go
index b4f1f07..dda4f99 100644
--- a/pie/carpointers_test.go
+++ b/pie/carpointers_test.go
@@ -816,3 +816,13 @@ func TestCarPointers_Diff(t *testing.T) {
 		})
 	}
 }
+
+func TestCarPointers_Strings(t *testing.T) {
+	assert.Equal(t, Strings(nil), carPointers(nil).Strings())
+
+	assert.Equal(t, Strings(nil), carPointers{}.Strings())
+
+	assert.Equal(t,
+		Strings{"a is green", "b is blue", "c is gray"},
+		carPointers{carPointerA, carPointerB, carPointerC}.Strings())
+}
diff --git a/pie/cars_test.go b/pie/cars_test.go
index 3c190d5..05f2086 100644
--- a/pie/cars_test.go
+++ b/pie/cars_test.go
@@ -797,3 +797,11 @@ func TestCars_Diff(t *testing.T) {
 		})
 	}
 }
+
+func TestCars_Strings(t *testing.T) {
+	assert.Equal(t, Strings(nil), cars{}.Strings())
+
+	assert.Equal(t,
+		Strings{"{a green}", "{bar yellow}", "{Baz black}"},
+		cars{car{"a", "green"}, car{"bar", "yellow"}, car{"Baz", "black"}}.Strings())
+}
diff --git a/pie/float64s_test.go b/pie/float64s_test.go
index 941ba37..8464faa 100644
--- a/pie/float64s_test.go
+++ b/pie/float64s_test.go
@@ -1063,3 +1063,11 @@ func TestFloat64s_Sequence(t *testing.T) {
 		})
 	}
 }
+
+func TestFloat64s_Strings(t *testing.T) {
+	assert.Equal(t, Strings(nil), Float64s{}.Strings())
+
+	assert.Equal(t,
+		Strings{"92.384", "823.324", "453"},
+		Float64s{92.384, 823.324, 453}.Strings())
+}
diff --git a/pie/ints_test.go b/pie/ints_test.go
index 45ef0a3..c675ddd 100644
--- a/pie/ints_test.go
+++ b/pie/ints_test.go
@@ -1028,3 +1028,11 @@ func TestInts_Sequence(t *testing.T) {
 		})
 	}
 }
+
+func TestInts_Strings(t *testing.T) {
+	assert.Equal(t, Strings(nil), Ints{}.Strings())
+
+	assert.Equal(t,
+		Strings{"92", "823", "453"},
+		Ints{92, 823, 453}.Strings())
+}
diff --git a/pie/strings_test.go b/pie/strings_test.go
index 39ebdee..9bd1336 100644
--- a/pie/strings_test.go
+++ b/pie/strings_test.go
@@ -978,3 +978,11 @@ func TestAppendNonDestructive(t *testing.T) {
 		t.Errorf("Expected %q, got %q", expected, x)
 	}
 }
+
+func TestStrings_Strings(t *testing.T) {
+	assert.Equal(t, Strings(nil), Strings{}.Strings())
+
+	assert.Equal(t,
+		Strings{"foo", "bar", "BAZ"},
+		Strings{"foo", "bar", "BAZ"}.Strings())
+}