Repository: elliotchance/pie
Pull request #175 (2022-03-22 17:50:18)

Version 2: Rewrite with type parameterized functions for Go 1.18+
v2 is a complete rewrite so that code generation is no longer required -
but we still have all the same benefits! There are some noteworthy
changes:

- There is no longer built-in types such as Float64s, Ints and Strings.
To chain operations you can use the new `Of`, `OfOrdered` and
`OfNumeric` functions.

- Append(), Extend() and Len() were not moved over, since the way they
would be used in v2 wouldn't provide any benefit over the standard
syntax. It may make sense for these to exist on the `Of*` functions -
but let's deal with that later.

- First() and Last() have been removed since there is no way to
substitute a zero value now. You will need to refactor these to use
FirstOr() and LastOr() respectively.

- Added String(), Float64() and Int() to help transform element types.


Patch:
diff --git a/.gitignore b/.gitignore
index 8e8555f..c50fae7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+.DS_Store
+
 # Binaries for programs and plugins
 *.exe
 *.exe~
diff --git a/README.md b/README.md
index 4d02531..b5b4a78 100644
--- a/README.md
+++ b/README.md
@@ -3,41 +3,34 @@
 [![Build Status](https://travis-ci.org/elliotchance/pie.svg?branch=master)](https://travis-ci.org/elliotchance/pie)
 [![codecov](https://codecov.io/gh/elliotchance/pie/branch/master/graph/badge.svg)](https://codecov.io/gh/elliotchance/pie)
 
-**Enjoy a slice!** `pie` is a code generator for dealing with slices that
-focuses on type safety, performance and immutability.
+**Enjoy a slice!** `pie` is a library of utility functions for common operations
+on slices and maps.
 
 - [Quick Start](#quick-start)
   * [Install/Update](#install-update)
-  * [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)
+  * [Example](#example)
 - [FAQ](#faq)
   * [What are the requirements?](#what-are-the-requirements-)
   * [What are the goals of `pie`?](#what-are-the-goals-of--pie--)
   * [How do I contribute a function?](#how-do-i-contribute-a-function-)
   * [Why is the emoji a slice of pizza instead of a pie?](#why-is-the-emoji-a-slice-of-pizza-instead-of-a-pie-)
-  * [How do I exclude generated files from code coverage?](#how-do-i-exclude-generated-files-from-code-coverage-)
 
 # Quick Start
 
 ## Install/Update
 
+If you are using (or require) Go 1.17 or below, you will have to
+[use v1](https://github.com/elliotchance/pie/v1).
+
 ```bash
-go get -u github.com/elliotchance/pie
+go get -u github.com/elliotchance/pie/v2
 ```
 
-## Built-in Types
-
-`pie` ships with some slice types ready to go (pun intended). These include:
+## Example
 
-- `type`[`Strings`](https://godoc.org/github.com/elliotchance/pie/pie#Strings)`[]string`
-- `type`[`Float64s`](https://godoc.org/github.com/elliotchance/pie/pie#Float64s)`[]float64`
-- `type`[`Ints`](https://godoc.org/github.com/elliotchance/pie/pie#Ints)`[]int`
+`pie` can be used in two ways, the first is to use the regular
+[parameterized functions](https://go.googlesource.com/proposal/+/master/design/15292/2013-12-type-params.md):
 
-These can be used without needing `go generate`. For example:
 
 ```go
 package main
@@ -46,734 +39,56 @@ import (
     "fmt"
     "strings"
 
-    "github.com/elliotchance/pie/pie"
+    "github.com/elliotchance/pie/v2"
 )
 
 func main() {
-    name := pie.Strings{"Bob", "Sally", "John", "Jane"}.
-        FilterNot(func (name string) bool {
+    names := pie.FilterNot([]string{"Bob", "Sally", "John", "Jane"},
+        func(name string) bool {
             return strings.HasPrefix(name, "J")
-        }).
-        Map(strings.ToUpper).
-        Last()
-
-    fmt.Println(name) // "SALLY"
-}
-```
-
-## Custom Types
-
-Annotate the slice type in your source code:
-
-```go
-type Car struct {
-    Name, Color string
-}
-
-//go:generate pie Cars.*
-type Cars []Car
-```
-
-Run `go generate`. This will create a file called `cars_pie.go`. You should
-commit this with the rest of your code. Run `go generate` any time you need to
-add more types.
-
-Now you can use the slices:
-
-```go
-cars := Cars{
-    {"Bob", "blue"},
-    {"Sally", "green"},
-    {"John", "red"},
-    {"Jane", "red"},
-}
-
-redCars := cars.Filter(func(car Car) bool {
-    return car.Color == "red"
-})
-
-// redCars = Cars{{"John", "red"}, {"Jane", "red"}}
-```
-
-Or, more complex operations can be chained:
-
-```go
-cars.FilterNot(func (car Car) {
-        return strings.HasPrefix(car.Name, "J")
-    }).
-    Map(func (car Car) Car {
-        car.Name = strings.ToUpper(car.Name)
-
-        return car
-    }).
-    Last()
-
-// Car{"SALLY", "green"}
-```
-
-## Custom Equality
-
-Some functions that compare elements, such as Contains will use the following
-method if it is available on the element type:
-
-```go
-func (a ElementType) Equals(b ElementType) bool
-```
-
-The `ElementType` must be the same for the receiver and argument and it must
-return a bool. Be careful to create the function on the pointer or non-pointer
-type that is used by the slice.
-
-Here is a minimal example:
+        })
 
-```go
-type Car struct {
-	Name, Color string
+    fmt.Println(names) // "[Bob Sally]"
 }
-
-type Cars []*Car // ElementType is *Car
-
-func (c *Car) Equals(c2 *Car) bool {
-	return c.Name == c2.Name
-}
-```
-
-## 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
-creates a lot of unused code. You can limit the functions generated by chaining
-the function names with a dot syntax, like:
-
-```go
-//go:generate pie myInts.Average.Sum myStrings.Filter
-```
-
-This will only generate `myInts.Average`, `myInts.Sum` and `myStrings.Filter`.
-
-# Functions
-
-Below is a summary of the available functions.
-
-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 |
-| --------------------------------------- | :----: | :----: | :----: | :--: | :------: | ----------- |
-| [`Abs`](#abs)                           |        | ✓      |        |      | n        | Abs is a function which returns the absolute value of all the elements in the slice.  |
-| [`All`](#all)                           | ✓      | ✓      | ✓      |      | n        | All will return true if all callbacks return true. It follows the same logic as the all() function in Python.  |
-| [`Any`](#any)                           | ✓      | ✓      | ✓      |      | n        | Any will return true if any callbacks return true. It follows the same logic as the any() function in Python.  |
-| [`Append`](#append)                     | ✓      | ✓      | ✓      |      | n        | Append will return a new slice with the elements appended to the end.  |
-| [`AreSorted`](#aresorted)               | ✓      | ✓      |        |      | n        | AreSorted will return true if the slice is already sorted. It is a wrapper for sort.SliceTypeAreSorted.  |
-| [`AreUnique`](#areunique)               | ✓      | ✓      |        |      | n        | AreUnique will return true if the slice contains elements that are all different (unique) from each other.  |
-| [`Average`](#average)                   |        | ✓      |        |      | n        | Average is the average of all of the elements, or zero if there are no elements.  |
-| [`Bottom`](#bottom)                     | ✓      | ✓      | ✓      |      | n        | Bottom will return n elements from bottom  |
-| [`Contains`](#contains) (E)             | ✓      | ✓      | ✓      |      | n        | Contains returns true if the element exists in the slice.  |
-| [`Diff`](#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.  |
-| [`DropTop`](#droptop)                   | ✓      | ✓      | ✓      |      | n        | DropTop will return the rest slice after dropping the top n elements if the slice has less elements then n that'll return empty slice if n < 0 it'll return empty slice.  |
-| [`DropWhile`](#dropwhile)               | ✓      | ✓      | ✓      |      | n        | Drop items from the slice while f(item) is true. Afterwards, return every element until the slice is empty. It follows the same logic as the dropwhile() function from itertools in Python.  |
-| [`Each`](#each)                         | ✓      | ✓      | ✓      |      | n        | Each is more condensed version of Transform that allows an action to happen on each elements and pass the original slice on.  |
-| [`Equals`](#equals) (E)                 | ✓      | ✓      | ✓      |      | n        | Equals compare elements from the start to the end,  |
-| [`Extend`](#extend)                     | ✓      | ✓      | ✓      |      | n        | Extend will return a new slice with the slices of elements appended to the end.  |
-| [`Filter`](#filter)                     | ✓      | ✓      | ✓      |      | n        | Filter will return a new slice containing only the elements that return true from the condition. The returned slice may contain zero elements (nil).  |
-| [`FilterNot`](#filternot)               | ✓      | ✓      | ✓      |      | n        | FilterNot works the same as Filter, with a negated condition. That is, it will return a new slice only containing the elements that returned false from the condition. The returned slice may contain zero elements (nil).  |
-| [`FindFirstUsing`](#findfirstusing)     | ✓      | ✓      | ✓      |      | n        | FindFirstUsing will return the index of the first element when the callback returns true or -1 if no element is found. It follows the same logic as the findIndex() function in Javascript.  |
-| [`First`](#first)                       | ✓      | ✓      | ✓      |      | 1        | First returns the first element, or zero. Also see FirstOr().  |
-| [`FirstOr`](#firstor)                   | ✓      | ✓      | ✓      |      | 1        | FirstOr returns the first element or a default value if there are no elements.  |
-| [`Float64s`](#float64s) (S)             | ✓      | ✓      | ✓      |      | n        | Float64s transforms each element to a float64.  |
-| [`Group`](#group)                       | ✓      | ✓      |        |      | n        | Group returns a map of the value with an individual count.  |
-| [`Intersect`](#intersect)               | ✓      | ✓      |        |      | n        | Intersect returns items that exist in all lists.  |
-| [`Insert`](#insert)                     | ✓      | ✓      | ✓      |      | n        | Insert a value at an index  |
-| [`Ints`](#ints) (S)                     | ✓      | ✓      | ✓      |      | n        | Ints transforms each element to an integer.  |
-| [`Join`](#join) (S)                     | ✓      | ✓      | ✓      |      | n        | Join returns a string from joining each of the elements.  |
-| [`JSONBytes`](#jsonbytes)               | ✓      | ✓      | ✓      |      | n        | JSONBytes returns the JSON encoded array as bytes.  |
-| [`JSONBytesIndent`](#jsonbytesindent)   | ✓      | ✓      | ✓      |      | n        | JSONBytesIndent returns the JSON encoded array as bytes with indent applied.  |
-| [`JSONString`](#jsonstring)             | ✓      | ✓      | ✓      |      | n        | JSONString returns the JSON encoded array as a string.  |
-| [`JSONStringIndent`](#jsonstringindent) | ✓      | ✓      | ✓      |      | n        | JSONStringIndent returns the JSON encoded array as a string with indent applied.  |
-| [`Keys`](#keys)                         |        |        |        | ✓    | n        | Keys returns the keys in the map. All of the items will be unique.  |
-| [`Last`](#last)                         | ✓      | ✓      | ✓      |      | 1        | Last returns the last element, or zero. Also see LastOr().  |
-| [`LastOr`](#lastor)                     | ✓      | ✓      | ✓      |      | 1        | LastOr returns the last element or a default value if there are no elements.  |
-| [`Len`](#len)                           | ✓      | ✓      | ✓      |      | 1        | Len returns the number of elements.  |
-| [`Map`](#map)                           | ✓      | ✓      | ✓      |      | n        | Map will return a new slice where each element has been mapped (transformed). The number of elements returned will always be the same as the input.  |
-| [`Max`](#max)                           | ✓      | ✓      |        |      | n        | Max is the maximum value, or zero.  |
-| [`Median`](#median)                     |        | ✓      |        |      | n        | Median returns the value separating the higher half from the lower half of a data sample.  |
-| [`Min`](#min)                           | ✓      | ✓      |        |      | n        | Min is the minimum value, or zero.  |
-| [`Mode`](#mode)                         | ✓      | ✓      | ✓      |      | n        | Mode returns a new slice containing the most frequently occuring values.  |
-| [`Pop`](#pop)                           | ✓      | ✓      | ✓      |      | n        | Pop the first element of the slice  |
-| [`Product`](#product)                   |        | ✓      |        |      | n        | Product is the product of all of the elements.  |
-| [`Random`](#random)                     | ✓      | ✓      | ✓      |      | 1        | Random returns a random element by your rand.Source, or zero  |
-| [`Reduce`](#reduce)                     | ✓      | ✓      |        |      | n        | Reduce continually applies the provided function over the slice. Reducing the elements to a single value.  |
-| [`Reverse`](#reverse)                   | ✓      | ✓      | ✓      |      | n        | Reverse returns a new copy of the slice with the elements ordered in reverse. This is useful when combined with Sort to get a descending sort order:  |
-| [`Send`](#send)                         | ✓      | ✓      | ✓      |      | n        | Send sends elements to channel in normal act it sends all elements but if func canceled it can be less  |
-| [`Sequence`](#sequence)                 |        | ✓      |        |      | n        | Sequence generates all numbers in range or returns nil if params invalid  |
-| [`SequenceUsing`](#sequenceusing)       | ✓      | ✓      | ✓      |      | n        | SequenceUsing generates slice in range using creator function  |
-| [`Shift`](#shift)                       | ✓      | ✓      | ✓      |      | n        | Shift will return two values: the shifted value and the rest slice.  |
-| [`Shuffle`](#shuffle)                   | ✓      | ✓      | ✓      |      | n        | Shuffle returns shuffled slice by your rand.Source  |
-| [`Sort`](#sort)                         | ✓      | ✓      |        |      | n⋅log(n) | Sort works similar to sort.SliceType(). However, unlike sort.SliceType the slice returned will be reallocated as to not modify the input slice.  |
-| [`SortStableUsing`](#sortstableusing)   | ✓      |        | ✓      |      | n⋅log(n) | SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the slice returned will be reallocated as to not modify the input slice.  |
-| [`SortUsing`](#sortusing)               | ✓      |        | ✓      |      | n⋅log(n) | SortUsing works similar to sort.Slice. However, unlike sort.Slice the slice returned will be reallocated as to not modify the input slice.  |
-| [`Stddev`](#stddev)                     |        | ✓      |        |      | n        | Stddev is the standard deviation  |
-| [`Strings`](#strings) (S)               | ✓      | ✓      | ✓      |      | n        | Strings transforms each element to a string.  |
-| [`SubSlice`](#subslice)                 | ✓      | ✓      | ✓      |      | n        | SubSlice will return the subSlice from start to end(excluded)  |
-| [`Sum`](#sum)                           |        | ✓      |        |      | n        | Sum is the sum of all of the elements.  |
-| [`Top`](#top)                           | ✓      | ✓      | ✓      |      | n        | 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.  |
-| [`StringsUsing`](#stringsusing)         | ✓      | ✓      | ✓      |      | n        | StringsUsing transforms each element to a string.  |
-| [`Unique`](#unique)                     | ✓      | ✓      |        |      | n        | Unique returns a new slice with all of the unique values.  |
-| [`Unshift`](#unshift)                   | ✓      | ✓      | ✓      |      | n        | Unshift adds one or more elements to the beginning of the slice and returns the new slice.  |
-| [`Values`](#values)                     |        |        |        | ✓    | n        | Values returns the values in the map.  |
-
-## Abs
-
-Abs is a function which returns the absolute value of all the
-elements in the slice.
-
-
-## All
-
-All will return true if all callbacks return true. It follows the same logic
-as the all() function in Python.
-
-
-If the list is empty then true is always returned.
-
-
-## Any
-
-Any will return true if any callbacks return true. It follows the same logic
-as the any() function in Python.
-
-
-If the list is empty then false is always returned.
-
-
-## Append
-
-Append will return a new slice with the elements appended to the end.
-
-
-It is acceptable to provide zero arguments.
-
-
-## AreSorted
-
-AreSorted will return true if the slice is already sorted. It is a wrapper
-for sort.SliceTypeAreSorted.
-
-
-## AreUnique
-
-AreUnique will return true if the slice contains elements that are all
-different (unique) from each other.
-
-
-## Average
-
-Average is the average of all of the elements, or zero if there are no
-elements.
-
-
-## Bottom
-
-Bottom will return n elements from bottom
-
-
-that means that elements is taken from the end of the slice
-for this [1,2,3] slice with n == 2 will be returned [3,2]
-if the slice has less elements then n that'll return all elements
-if n < 0 it'll return empty slice.
-
-
-## Contains
-
-Contains returns true if the element exists in the slice.
-
-
-When using slices of pointers it will only compare by address, not value.
-
-
-## Diff
-
-Diff returns the elements that needs to be added or removed from the first
-slice to have the same elements in the second slice.
-
-
-The order of elements is not taken into consideration, so the slices are
-treated sets that allow duplicate items.
-
-
-The added and removed returned may be blank respectively, or contain upto as
-many elements that exists in the largest slice.
-
-
-## DropTop
-
-DropTop will return the rest slice after dropping the top n elements
-if the slice has less elements then n that'll return empty slice
-if n < 0 it'll return empty slice.
-
-
-## DropWhile
-
-Drop items from the slice while f(item) is true.
-Afterwards, return every element until the slice is empty. It follows the same logic as the dropwhile() function from itertools in Python.
-
-
-## Each
-
-Each is more condensed version of Transform that allows an action to happen
-on each elements and pass the original slice on.
-
-
-```go
-cars.Each(func (car *Car) {
-    fmt.Printf("Car color is: %s\n", car.Color)
-})
-
 ```
 
-Pie will not ensure immutability on items passed in so they can be
-manipulated, if you choose to do it this way, for example:
+Or, if you need to chain multiple operations you can use one of:
 
+- `Of` - works with any element type, but functions are limited.
+- `OfOrders` - only works with numbers and strings, but has more functions.
+- `OfNumeric` - only works with numbers, but has all functions.
 
 ```go
-// Set all car colors to Red.
-cars.Each(func (car *Car) {
-    car.Color = "Red"
-})
-
-```
-
-
-
-## Equals
-
-Equals compare elements from the start to the end,
-
-
-if they are the same is considered the slices are equal if all elements are the same is considered the slices are equal
-if each slice == nil is considered that they're equal
-
-
-if element realizes Equals interface it uses that method, in other way uses default compare
-
-
-## Extend
-
-Extend will return a new slice with the slices of elements appended to the
-end.
-
-
-It is acceptable to provide zero arguments.
-
-
-## Filter
-
-Filter will return a new slice containing only the elements that return
-true from the condition. The returned slice may contain zero elements (nil).
-
-
-FilterNot works in the opposite way of Filter.
-
-
-## FilterNot
-
-FilterNot works the same as Filter, with a negated condition. That is, it will
-return a new slice only containing the elements that returned false from the
-condition. The returned slice may contain zero elements (nil).
-
-
-## FindFirstUsing
-
-FindFirstUsing will return the index of the first element when the callback returns true or -1 if no element is found.
-It follows the same logic as the findIndex() function in Javascript.
-
-
-If the list is empty then -1 is always returned.
-
-
-## First
-
-First returns the first element, or zero. Also see FirstOr().
-
-
-## FirstOr
-
-FirstOr returns the first element or a default value if there are no
-elements.
-
-
-## Float64s
-
-Float64s transforms each element to a float64.
-
-
-## Group
-
-Group returns a map of the value with an individual count.
-
-
-
-
-## Intersect
-
-Intersect returns items that exist in all lists.
-
-
-It returns slice without any duplicates.
-If zero slice arguments are provided, then nil is returned.
-
-
-## Insert
-
-Insert a value at an index
-
-
-## Ints
-
-Ints transforms each element to an integer.
-
-
-## Join
-
-Join returns a string from joining each of the elements.
-
-
-## JSONBytes
-
-JSONBytes returns the JSON encoded array as bytes.
-
-
-One important thing to note is that it will treat a nil slice as an empty
-slice to ensure that the JSON value return is always an array.
-
-
-## JSONBytesIndent
-
-JSONBytesIndent returns the JSON encoded array as bytes with indent applied.
-
-
-One important thing to note is that it will treat a nil slice as an empty
-slice to ensure that the JSON value return is always an array. See
-json.MarshalIndent for details.
-
-
-## JSONString
-
-JSONString returns the JSON encoded array as a string.
-
-
-One important thing to note is that it will treat a nil slice as an empty
-slice to ensure that the JSON value return is always an array.
-
-
-## JSONStringIndent
-
-JSONStringIndent returns the JSON encoded array as a string with indent applied.
-
-
-One important thing to note is that it will treat a nil slice as an empty
-slice to ensure that the JSON value return is always an array. See
-json.MarshalIndent for details.
-
-
-## Keys
-
-Keys returns the keys in the map. All of the items will be unique.
-
-
-Due to Go's randomization of iterating maps the order is not deterministic.
-
-
-## Last
-
-Last returns the last element, or zero. Also see LastOr().
-
-
-## LastOr
-
-LastOr returns the last element or a default value if there are no elements.
-
-
-## Len
-
-Len returns the number of elements.
-
-
-## Map
-
-Map will return a new slice where each element has been mapped (transformed).
-The number of elements returned will always be the same as the input.
-
-
-Be careful when using this with slices of pointers. If you modify the input
-value it will affect the original slice. Be sure to return a new allocated
-object or deep copy the existing one.
-
-
-## Max
-
-Max is the maximum value, or zero.
-
-
-## Median
-
-Median returns the value separating the higher half from the lower half of a
-data sample.
-
-
-Zero is returned if there are no elements in the slice.
-
-
-If the number of elements is even, then the ElementType mean of the two "median values"
-is returned.
-
-
-## Min
-
-Min is the minimum value, or zero.
-
-
-## Mode
-
-Mode returns a new slice containing the most frequently occuring values.
-
-
-The number of items returned may be the same as the input or less. It will
-never return zero items unless the input slice has zero items.
-
-
-## Pop
-
-Pop the first element of the slice
+package main
 
+import (
+    "fmt"
+    "strings"
 
-Usage Example:
+    "github.com/elliotchance/pie/v2"
+)
 
+func main() {
+    name := pie.Of([]string{"Bob", "Sally", "John", "Jane"}).
+        FilterNot(func(name string) bool {
+            return strings.HasPrefix(name, "J")
+        }).
+        Map(strings.ToUpper).
+        LastOr("")
 
-```go
-type knownGreetings []string
-greetings := knownGreetings{"ciao", "hello", "hola"}
-for greeting := greetings.Pop(); greeting != nil; greeting = greetings.Pop() {
-    fmt.Println(*greeting)
+    fmt.Println(name) // "SALLY"
 }
-
-```
-
-## Product
-
-Product is the product of all of the elements.
-
-
-## Random
-
-Random returns a random element by your rand.Source, or zero
-
-
-## Reduce
-
-Reduce continually applies the provided function
-over the slice. Reducing the elements to a single value.
-
-
-Returns a zero value of ElementType if there are no elements in the slice. It will panic if the reducer is nil and the slice has more than one element (required to invoke reduce).
-Otherwise returns result of applying reducer from left to right.
-
-
-## Reverse
-
-Reverse returns a new copy of the slice with the elements ordered in reverse.
-This is useful when combined with Sort to get a descending sort order:
-
-
-```go
-ss.Sort().Reverse()
-
 ```
 
-
-
-## Send
-
-Send sends elements to channel
-in normal act it sends all elements but if func canceled it can be less
-
-
-it locks execution of gorutine
-it doesn't close channel after work
-returns sended elements if len(this) != len(old) considered func was canceled
-
-
-## Sequence
-
-Sequence generates all numbers in range or returns nil if params invalid
-
-
-There are 3 variations to generate:
-		1. [0, n).
-	2. [min, max).
-	3. [min, max) with step.
-
-
-if len(params) == 1 considered that will be returned slice between 0 and n,
-where n is the first param, [0, n).
-if len(params) == 2 considered that will be returned slice between min and max,
-where min is the first param, max is the second, [min, max).
-if len(params) > 2 considered that will be returned slice between min and max with step,
-where min is the first param, max is the second, step is the third one, [min, max) with step,
-others params will be ignored
-
-
-## SequenceUsing
-
-SequenceUsing generates slice in range using creator function
-
-
-There are 3 variations to generate:
-		1. [0, n).
-	2. [min, max).
-	3. [min, max) with step.
-
-
-if len(params) == 1 considered that will be returned slice between 0 and n,
-where n is the first param, [0, n).
-if len(params) == 2 considered that will be returned slice between min and max,
-where min is the first param, max is the second, [min, max).
-if len(params) > 2 considered that will be returned slice between min and max with step,
-where min is the first param, max is the second, step is the third one, [min, max) with step,
-others params will be ignored
-
-
-## Shift
-
-Shift will return two values: the shifted value and the rest slice.
-
-
-## Shuffle
-
-Shuffle returns shuffled slice by your rand.Source
-
-
-## Sort
-
-Sort works similar to sort.SliceType(). However, unlike sort.SliceType the
-slice returned will be reallocated as to not modify the input slice.
-
-
-See Reverse() and AreSorted().
-
-
-## SortStableUsing
-
-SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the
-slice returned will be reallocated as to not modify the input slice.
-
-
-## SortUsing
-
-SortUsing works similar to sort.Slice. However, unlike sort.Slice the
-slice returned will be reallocated as to not modify the input slice.
-
-
-## Stddev
-
-Stddev is the standard deviation
-
-
-## Strings
-
-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:
-
-
-```go
-fmt.Sprintf("%v")
-
-```
-
-
-
-## SubSlice
-
-SubSlice will return the subSlice from start to end(excluded)
-
-
-Condition 1: If start < 0 or end < 0, nil is returned.
-Condition 2: If start >= end, nil is returned.
-Condition 3: Return all elements that exist in the range provided,
-if start or end is out of bounds, zero items will be placed.
-
-
-## Sum
-
-Sum is the sum of all of the elements.
-
-
-## Top
-
-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.
-
-
-## StringsUsing
-
-StringsUsing transforms each element to a string.
-
-
-## Unique
-
-Unique returns a new slice with all of the unique values.
-
-
-The items will be returned in a randomized order, even with the same input.
-
-
-The number of items returned may be the same as the input or less. It will
-never return zero items unless then input slice has zero items.
-
-
-A slice with zero elements is considered to be unique.
-
-
-See AreUnique().
-
-
-## Unshift
-
-Unshift adds one or more elements to the beginning of the slice
-and returns the new slice.
-
-
-## Values
-
-Values returns the values in the map.
-
-
-Due to Go's randomization of iterating maps the order is not deterministic.
-
+You can find the
+[full documentation here](https://pkg.go.dev/github.com/elliotchance/pie/v2).
 
 # FAQ
 
 ## What are the requirements?
 
-`pie` supports many Go versions, all the way back to Go 1.8.
+`pie` v2 only supports Go 1.18+. If you have an older version you can
+[use v1](https://github.com/elliotchance/pie/v1).
 
 ## What are the goals of `pie`?
 
@@ -786,8 +101,8 @@ implementations otherwise there's no point in this library existing.
 3. **Nil-safe.** All of the functions will happily accept nil and treat them as
 empty slices. Apart from less possible panics, it makes it easier to chain.
 
-4. **Immutable.** Functions never modify inputs, unlike some built-ins such as
-`sort.Strings`.
+4. **Immutable.** Functions never modify inputs (except in cases where it would
+be illogical), unlike some built-ins such as `sort.Strings`.
 
 ## How do I contribute a function?
 
@@ -795,29 +110,13 @@ Pull requests are always welcome.
 
 Here is a comprehensive list of steps to follow to add a new function:
 
-1. Create a new file in the `functions/` directory. The file should be named the
-same as the function. You must include documentation for your function.
-
-2. Update `functions/main.go` to register the new function by adding an entry to
-`Functions`. Make sure you choose the correct `For` value that is appropriate
-for your function.
-
-3. Run `go generate && go install && go generate ./...`. The first
-`generate` is to create the pie templates, `install` will update your binary for
-the annotations and the second `generate` will use the newly created templates
-to update the generated code for the internal types. If you encounter errors
-with your code you can safely rerun the command above.
-
-4. If you chose `ForAll` or `ForStructs`, then you must add unit tests to
-`pie/carpointers_test.go` and `pie/cars_test.go`.
+1. Create a new file for your function (tip: copy an existing file can be
+quicker). Add your implmentation and comment.
 
-5. If you chose `ForAll`, `ForNumbersAndStrings` or `ForNumbers`, then you must
-add unit tests to `pie/float64s_test.go` and `pie/ints_test.go`.
+2. Create appropriate tests.
 
-6. If you chose `ForAll` or `ForStrings`, then you must add unit tests to
-`pie/strings_test.go`.
-
-7. If you chose `ForMaps`, then you must add unit tests to `pie/currencies.go`.
+3. If your function accepts a slice, it should also be added to the `OfSlice`
+API (see `of.go`).
 
 ## Why is the emoji a slice of pizza instead of a pie?
 
@@ -826,21 +125,3 @@ emoji. I liked pie, but then I found out that the pie emoji is not fully
 supported everywhere. I didn't want to change the name of the project to cake,
 but pizza pie still made sense. I'm not sure if I will change it back to a pie
 later.
-
-## How do I exclude generated files from code coverage?
-
-Go does not provide a way to exclude code coverage from specific files or lines
-with comments. However, you can remove the `_pie.go` files from the code
-coverage report before it is published:
-
-```bash
-go test -race -coverprofile=coverage.txt -covermode=atomic
-sed -i '/_pie\.go/d' ./coverage.txt
-```
-
-If you are running on macOS, you will need a slightly different syntax for
-`sed`:
-
-```bash
-sed -i '' /_pie\.go/d' ./coverage.txt
-```
diff --git a/v1/README.md b/v1/README.md
new file mode 100644
index 0000000..fe927d5
--- /dev/null
+++ b/v1/README.md
@@ -0,0 +1,854 @@
+# 🍕 `github.com/elliotchance/pie`
+[![GoDoc](https://godoc.org/github.com/elliotchance/pie?status.svg)](https://godoc.org/github.com/elliotchance/pie)
+[![Build Status](https://travis-ci.org/elliotchance/pie.svg?branch=master)](https://travis-ci.org/elliotchance/pie)
+[![codecov](https://codecov.io/gh/elliotchance/pie/branch/master/graph/badge.svg)](https://codecov.io/gh/elliotchance/pie)
+
+**Enjoy a slice!** `pie` is a code generator for dealing with slices that
+focuses on type safety, performance and immutability.
+
+**Are you using Go 1.18+? You can now
+[use v2](https://github.com/elliotchance/pie) that requires no code
+generation!**
+
+- [Quick Start](#quick-start)
+  * [Install/Update](#install-update)
+  * [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)
+  * [What are the requirements?](#what-are-the-requirements-)
+  * [What are the goals of `pie`?](#what-are-the-goals-of--pie--)
+  * [How do I contribute a function?](#how-do-i-contribute-a-function-)
+  * [Why is the emoji a slice of pizza instead of a pie?](#why-is-the-emoji-a-slice-of-pizza-instead-of-a-pie-)
+  * [How do I exclude generated files from code coverage?](#how-do-i-exclude-generated-files-from-code-coverage-)
+
+# Quick Start
+
+## Install/Update
+
+**Are you using Go 1.18+? You can now
+[use v2](https://github.com/elliotchance/pie) that requires no code
+generation!**
+
+```bash
+go get -u github.com/elliotchance/pie
+```
+
+## Built-in Types
+
+`pie` ships with some slice types ready to go (pun intended). These include:
+
+- `type`[`Strings`](https://godoc.org/github.com/elliotchance/pie/pie#Strings)`[]string`
+- `type`[`Float64s`](https://godoc.org/github.com/elliotchance/pie/pie#Float64s)`[]float64`
+- `type`[`Ints`](https://godoc.org/github.com/elliotchance/pie/pie#Ints)`[]int`
+
+These can be used without needing `go generate`. For example:
+
+```go
+package main
+
+import (
+    "fmt"
+    "strings"
+
+    "github.com/elliotchance/pie/pie"
+)
+
+func main() {
+    name := pie.Strings{"Bob", "Sally", "John", "Jane"}.
+        FilterNot(func (name string) bool {
+            return strings.HasPrefix(name, "J")
+        }).
+        Map(strings.ToUpper).
+        Last()
+
+    fmt.Println(name) // "SALLY"
+}
+```
+
+## Custom Types
+
+Annotate the slice type in your source code:
+
+```go
+type Car struct {
+    Name, Color string
+}
+
+//go:generate pie Cars.*
+type Cars []Car
+```
+
+Run `go generate`. This will create a file called `cars_pie.go`. You should
+commit this with the rest of your code. Run `go generate` any time you need to
+add more types.
+
+Now you can use the slices:
+
+```go
+cars := Cars{
+    {"Bob", "blue"},
+    {"Sally", "green"},
+    {"John", "red"},
+    {"Jane", "red"},
+}
+
+redCars := cars.Filter(func(car Car) bool {
+    return car.Color == "red"
+})
+
+// redCars = Cars{{"John", "red"}, {"Jane", "red"}}
+```
+
+Or, more complex operations can be chained:
+
+```go
+cars.FilterNot(func (car Car) {
+        return strings.HasPrefix(car.Name, "J")
+    }).
+    Map(func (car Car) Car {
+        car.Name = strings.ToUpper(car.Name)
+
+        return car
+    }).
+    Last()
+
+// Car{"SALLY", "green"}
+```
+
+## Custom Equality
+
+Some functions that compare elements, such as Contains will use the following
+method if it is available on the element type:
+
+```go
+func (a ElementType) Equals(b ElementType) bool
+```
+
+The `ElementType` must be the same for the receiver and argument and it must
+return a bool. Be careful to create the function on the pointer or non-pointer
+type that is used by the slice.
+
+Here is a minimal example:
+
+```go
+type Car struct {
+	Name, Color string
+}
+
+type Cars []*Car // ElementType is *Car
+
+func (c *Car) Equals(c2 *Car) bool {
+	return c.Name == c2.Name
+}
+```
+
+## 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
+creates a lot of unused code. You can limit the functions generated by chaining
+the function names with a dot syntax, like:
+
+```go
+//go:generate pie myInts.Average.Sum myStrings.Filter
+```
+
+This will only generate `myInts.Average`, `myInts.Sum` and `myStrings.Filter`.
+
+# Functions
+
+Below is a summary of the available functions.
+
+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 |
+| --------------------------------------- | :----: | :----: | :----: | :--: | :------: | ----------- |
+| [`Abs`](#abs)                           |        | ✓      |        |      | n        | Abs is a function which returns the absolute value of all the elements in the slice.  |
+| [`All`](#all)                           | ✓      | ✓      | ✓      |      | n        | All will return true if all callbacks return true. It follows the same logic as the all() function in Python.  |
+| [`Any`](#any)                           | ✓      | ✓      | ✓      |      | n        | Any will return true if any callbacks return true. It follows the same logic as the any() function in Python.  |
+| [`Append`](#append)                     | ✓      | ✓      | ✓      |      | n        | Append will return a new slice with the elements appended to the end.  |
+| [`AreSorted`](#aresorted)               | ✓      | ✓      |        |      | n        | AreSorted will return true if the slice is already sorted. It is a wrapper for sort.SliceTypeAreSorted.  |
+| [`AreUnique`](#areunique)               | ✓      | ✓      |        |      | n        | AreUnique will return true if the slice contains elements that are all different (unique) from each other.  |
+| [`Average`](#average)                   |        | ✓      |        |      | n        | Average is the average of all of the elements, or zero if there are no elements.  |
+| [`Bottom`](#bottom)                     | ✓      | ✓      | ✓      |      | n        | Bottom will return n elements from bottom  |
+| [`Contains`](#contains) (E)             | ✓      | ✓      | ✓      |      | n        | Contains returns true if the element exists in the slice.  |
+| [`Diff`](#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.  |
+| [`DropTop`](#droptop)                   | ✓      | ✓      | ✓      |      | n        | DropTop will return the rest slice after dropping the top n elements if the slice has less elements then n that'll return empty slice if n < 0 it'll return empty slice.  |
+| [`DropWhile`](#dropwhile)               | ✓      | ✓      | ✓      |      | n        | Drop items from the slice while f(item) is true. Afterwards, return every element until the slice is empty. It follows the same logic as the dropwhile() function from itertools in Python.  |
+| [`Each`](#each)                         | ✓      | ✓      | ✓      |      | n        | Each is more condensed version of Transform that allows an action to happen on each elements and pass the original slice on.  |
+| [`Equals`](#equals) (E)                 | ✓      | ✓      | ✓      |      | n        | Equals compare elements from the start to the end,  |
+| [`Extend`](#extend)                     | ✓      | ✓      | ✓      |      | n        | Extend will return a new slice with the slices of elements appended to the end.  |
+| [`Filter`](#filter)                     | ✓      | ✓      | ✓      |      | n        | Filter will return a new slice containing only the elements that return true from the condition. The returned slice may contain zero elements (nil).  |
+| [`FilterNot`](#filternot)               | ✓      | ✓      | ✓      |      | n        | FilterNot works the same as Filter, with a negated condition. That is, it will return a new slice only containing the elements that returned false from the condition. The returned slice may contain zero elements (nil).  |
+| [`FindFirstUsing`](#findfirstusing)     | ✓      | ✓      | ✓      |      | n        | FindFirstUsing will return the index of the first element when the callback returns true or -1 if no element is found. It follows the same logic as the findIndex() function in Javascript.  |
+| [`First`](#first)                       | ✓      | ✓      | ✓      |      | 1        | First returns the first element, or zero. Also see FirstOr().  |
+| [`FirstOr`](#firstor)                   | ✓      | ✓      | ✓      |      | 1        | FirstOr returns the first element or a default value if there are no elements.  |
+| [`Float64s`](#float64s) (S)             | ✓      | ✓      | ✓      |      | n        | Float64s transforms each element to a float64.  |
+| [`Group`](#group)                       | ✓      | ✓      |        |      | n        | Group returns a map of the value with an individual count.  |
+| [`Intersect`](#intersect)               | ✓      | ✓      |        |      | n        | Intersect returns items that exist in all lists.  |
+| [`Insert`](#insert)                     | ✓      | ✓      | ✓      |      | n        | Insert a value at an index  |
+| [`Ints`](#ints) (S)                     | ✓      | ✓      | ✓      |      | n        | Ints transforms each element to an integer.  |
+| [`Join`](#join) (S)                     | ✓      | ✓      | ✓      |      | n        | Join returns a string from joining each of the elements.  |
+| [`JSONBytes`](#jsonbytes)               | ✓      | ✓      | ✓      |      | n        | JSONBytes returns the JSON encoded array as bytes.  |
+| [`JSONBytesIndent`](#jsonbytesindent)   | ✓      | ✓      | ✓      |      | n        | JSONBytesIndent returns the JSON encoded array as bytes with indent applied.  |
+| [`JSONString`](#jsonstring)             | ✓      | ✓      | ✓      |      | n        | JSONString returns the JSON encoded array as a string.  |
+| [`JSONStringIndent`](#jsonstringindent) | ✓      | ✓      | ✓      |      | n        | JSONStringIndent returns the JSON encoded array as a string with indent applied.  |
+| [`Keys`](#keys)                         |        |        |        | ✓    | n        | Keys returns the keys in the map. All of the items will be unique.  |
+| [`Last`](#last)                         | ✓      | ✓      | ✓      |      | 1        | Last returns the last element, or zero. Also see LastOr().  |
+| [`LastOr`](#lastor)                     | ✓      | ✓      | ✓      |      | 1        | LastOr returns the last element or a default value if there are no elements.  |
+| [`Len`](#len)                           | ✓      | ✓      | ✓      |      | 1        | Len returns the number of elements.  |
+| [`Map`](#map)                           | ✓      | ✓      | ✓      |      | n        | Map will return a new slice where each element has been mapped (transformed). The number of elements returned will always be the same as the input.  |
+| [`Max`](#max)                           | ✓      | ✓      |        |      | n        | Max is the maximum value, or zero.  |
+| [`Median`](#median)                     |        | ✓      |        |      | n        | Median returns the value separating the higher half from the lower half of a data sample.  |
+| [`Min`](#min)                           | ✓      | ✓      |        |      | n        | Min is the minimum value, or zero.  |
+| [`Mode`](#mode)                         | ✓      | ✓      | ✓      |      | n        | Mode returns a new slice containing the most frequently occuring values.  |
+| [`Pop`](#pop)                           | ✓      | ✓      | ✓      |      | n        | Pop the first element of the slice  |
+| [`Product`](#product)                   |        | ✓      |        |      | n        | Product is the product of all of the elements.  |
+| [`Random`](#random)                     | ✓      | ✓      | ✓      |      | 1        | Random returns a random element by your rand.Source, or zero  |
+| [`Reduce`](#reduce)                     | ✓      | ✓      |        |      | n        | Reduce continually applies the provided function over the slice. Reducing the elements to a single value.  |
+| [`Reverse`](#reverse)                   | ✓      | ✓      | ✓      |      | n        | Reverse returns a new copy of the slice with the elements ordered in reverse. This is useful when combined with Sort to get a descending sort order:  |
+| [`Send`](#send)                         | ✓      | ✓      | ✓      |      | n        | Send sends elements to channel in normal act it sends all elements but if func canceled it can be less  |
+| [`Sequence`](#sequence)                 |        | ✓      |        |      | n        | Sequence generates all numbers in range or returns nil if params invalid  |
+| [`SequenceUsing`](#sequenceusing)       | ✓      | ✓      | ✓      |      | n        | SequenceUsing generates slice in range using creator function  |
+| [`Shift`](#shift)                       | ✓      | ✓      | ✓      |      | n        | Shift will return two values: the shifted value and the rest slice.  |
+| [`Shuffle`](#shuffle)                   | ✓      | ✓      | ✓      |      | n        | Shuffle returns shuffled slice by your rand.Source  |
+| [`Sort`](#sort)                         | ✓      | ✓      |        |      | n⋅log(n) | Sort works similar to sort.SliceType(). However, unlike sort.SliceType the slice returned will be reallocated as to not modify the input slice.  |
+| [`SortStableUsing`](#sortstableusing)   | ✓      |        | ✓      |      | n⋅log(n) | SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the slice returned will be reallocated as to not modify the input slice.  |
+| [`SortUsing`](#sortusing)               | ✓      |        | ✓      |      | n⋅log(n) | SortUsing works similar to sort.Slice. However, unlike sort.Slice the slice returned will be reallocated as to not modify the input slice.  |
+| [`Stddev`](#stddev)                     |        | ✓      |        |      | n        | Stddev is the standard deviation  |
+| [`Strings`](#strings) (S)               | ✓      | ✓      | ✓      |      | n        | Strings transforms each element to a string.  |
+| [`SubSlice`](#subslice)                 | ✓      | ✓      | ✓      |      | n        | SubSlice will return the subSlice from start to end(excluded)  |
+| [`Sum`](#sum)                           |        | ✓      |        |      | n        | Sum is the sum of all of the elements.  |
+| [`Top`](#top)                           | ✓      | ✓      | ✓      |      | n        | 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.  |
+| [`StringsUsing`](#stringsusing)         | ✓      | ✓      | ✓      |      | n        | StringsUsing transforms each element to a string.  |
+| [`Unique`](#unique)                     | ✓      | ✓      |        |      | n        | Unique returns a new slice with all of the unique values.  |
+| [`Unshift`](#unshift)                   | ✓      | ✓      | ✓      |      | n        | Unshift adds one or more elements to the beginning of the slice and returns the new slice.  |
+| [`Values`](#values)                     |        |        |        | ✓    | n        | Values returns the values in the map.  |
+
+## Abs
+
+Abs is a function which returns the absolute value of all the
+elements in the slice.
+
+
+## All
+
+All will return true if all callbacks return true. It follows the same logic
+as the all() function in Python.
+
+
+If the list is empty then true is always returned.
+
+
+## Any
+
+Any will return true if any callbacks return true. It follows the same logic
+as the any() function in Python.
+
+
+If the list is empty then false is always returned.
+
+
+## Append
+
+Append will return a new slice with the elements appended to the end.
+
+
+It is acceptable to provide zero arguments.
+
+
+## AreSorted
+
+AreSorted will return true if the slice is already sorted. It is a wrapper
+for sort.SliceTypeAreSorted.
+
+
+## AreUnique
+
+AreUnique will return true if the slice contains elements that are all
+different (unique) from each other.
+
+
+## Average
+
+Average is the average of all of the elements, or zero if there are no
+elements.
+
+
+## Bottom
+
+Bottom will return n elements from bottom
+
+
+that means that elements is taken from the end of the slice
+for this [1,2,3] slice with n == 2 will be returned [3,2]
+if the slice has less elements then n that'll return all elements
+if n < 0 it'll return empty slice.
+
+
+## Contains
+
+Contains returns true if the element exists in the slice.
+
+
+When using slices of pointers it will only compare by address, not value.
+
+
+## Diff
+
+Diff returns the elements that needs to be added or removed from the first
+slice to have the same elements in the second slice.
+
+
+The order of elements is not taken into consideration, so the slices are
+treated sets that allow duplicate items.
+
+
+The added and removed returned may be blank respectively, or contain upto as
+many elements that exists in the largest slice.
+
+
+## DropTop
+
+DropTop will return the rest slice after dropping the top n elements
+if the slice has less elements then n that'll return empty slice
+if n < 0 it'll return empty slice.
+
+
+## DropWhile
+
+Drop items from the slice while f(item) is true.
+Afterwards, return every element until the slice is empty. It follows the same logic as the dropwhile() function from itertools in Python.
+
+
+## Each
+
+Each is more condensed version of Transform that allows an action to happen
+on each elements and pass the original slice on.
+
+
+```go
+cars.Each(func (car *Car) {
+    fmt.Printf("Car color is: %s\n", car.Color)
+})
+
+```
+
+Pie will not ensure immutability on items passed in so they can be
+manipulated, if you choose to do it this way, for example:
+
+
+```go
+// Set all car colors to Red.
+cars.Each(func (car *Car) {
+    car.Color = "Red"
+})
+
+```
+
+
+
+## Equals
+
+Equals compare elements from the start to the end,
+
+
+if they are the same is considered the slices are equal if all elements are the same is considered the slices are equal
+if each slice == nil is considered that they're equal
+
+
+if element realizes Equals interface it uses that method, in other way uses default compare
+
+
+## Extend
+
+Extend will return a new slice with the slices of elements appended to the
+end.
+
+
+It is acceptable to provide zero arguments.
+
+
+## Filter
+
+Filter will return a new slice containing only the elements that return
+true from the condition. The returned slice may contain zero elements (nil).
+
+
+FilterNot works in the opposite way of Filter.
+
+
+## FilterNot
+
+FilterNot works the same as Filter, with a negated condition. That is, it will
+return a new slice only containing the elements that returned false from the
+condition. The returned slice may contain zero elements (nil).
+
+
+## FindFirstUsing
+
+FindFirstUsing will return the index of the first element when the callback returns true or -1 if no element is found.
+It follows the same logic as the findIndex() function in Javascript.
+
+
+If the list is empty then -1 is always returned.
+
+
+## First
+
+First returns the first element, or zero. Also see FirstOr().
+
+
+## FirstOr
+
+FirstOr returns the first element or a default value if there are no
+elements.
+
+
+## Float64s
+
+Float64s transforms each element to a float64.
+
+
+## Group
+
+Group returns a map of the value with an individual count.
+
+
+
+
+## Intersect
+
+Intersect returns items that exist in all lists.
+
+
+It returns slice without any duplicates.
+If zero slice arguments are provided, then nil is returned.
+
+
+## Insert
+
+Insert a value at an index
+
+
+## Ints
+
+Ints transforms each element to an integer.
+
+
+## Join
+
+Join returns a string from joining each of the elements.
+
+
+## JSONBytes
+
+JSONBytes returns the JSON encoded array as bytes.
+
+
+One important thing to note is that it will treat a nil slice as an empty
+slice to ensure that the JSON value return is always an array.
+
+
+## JSONBytesIndent
+
+JSONBytesIndent returns the JSON encoded array as bytes with indent applied.
+
+
+One important thing to note is that it will treat a nil slice as an empty
+slice to ensure that the JSON value return is always an array. See
+json.MarshalIndent for details.
+
+
+## JSONString
+
+JSONString returns the JSON encoded array as a string.
+
+
+One important thing to note is that it will treat a nil slice as an empty
+slice to ensure that the JSON value return is always an array.
+
+
+## JSONStringIndent
+
+JSONStringIndent returns the JSON encoded array as a string with indent applied.
+
+
+One important thing to note is that it will treat a nil slice as an empty
+slice to ensure that the JSON value return is always an array. See
+json.MarshalIndent for details.
+
+
+## Keys
+
+Keys returns the keys in the map. All of the items will be unique.
+
+
+Due to Go's randomization of iterating maps the order is not deterministic.
+
+
+## Last
+
+Last returns the last element, or zero. Also see LastOr().
+
+
+## LastOr
+
+LastOr returns the last element or a default value if there are no elements.
+
+
+## Len
+
+Len returns the number of elements.
+
+
+## Map
+
+Map will return a new slice where each element has been mapped (transformed).
+The number of elements returned will always be the same as the input.
+
+
+Be careful when using this with slices of pointers. If you modify the input
+value it will affect the original slice. Be sure to return a new allocated
+object or deep copy the existing one.
+
+
+## Max
+
+Max is the maximum value, or zero.
+
+
+## Median
+
+Median returns the value separating the higher half from the lower half of a
+data sample.
+
+
+Zero is returned if there are no elements in the slice.
+
+
+If the number of elements is even, then the ElementType mean of the two "median values"
+is returned.
+
+
+## Min
+
+Min is the minimum value, or zero.
+
+
+## Mode
+
+Mode returns a new slice containing the most frequently occuring values.
+
+
+The number of items returned may be the same as the input or less. It will
+never return zero items unless the input slice has zero items.
+
+
+## Pop
+
+Pop the first element of the slice
+
+
+Usage Example:
+
+
+```go
+type knownGreetings []string
+greetings := knownGreetings{"ciao", "hello", "hola"}
+for greeting := greetings.Pop(); greeting != nil; greeting = greetings.Pop() {
+    fmt.Println(*greeting)
+}
+
+```
+
+## Product
+
+Product is the product of all of the elements.
+
+
+## Random
+
+Random returns a random element by your rand.Source, or zero
+
+
+## Reduce
+
+Reduce continually applies the provided function
+over the slice. Reducing the elements to a single value.
+
+
+Returns a zero value of ElementType if there are no elements in the slice. It will panic if the reducer is nil and the slice has more than one element (required to invoke reduce).
+Otherwise returns result of applying reducer from left to right.
+
+
+## Reverse
+
+Reverse returns a new copy of the slice with the elements ordered in reverse.
+This is useful when combined with Sort to get a descending sort order:
+
+
+```go
+ss.Sort().Reverse()
+
+```
+
+
+
+## Send
+
+Send sends elements to channel
+in normal act it sends all elements but if func canceled it can be less
+
+
+it locks execution of gorutine
+it doesn't close channel after work
+returns sended elements if len(this) != len(old) considered func was canceled
+
+
+## Sequence
+
+Sequence generates all numbers in range or returns nil if params invalid
+
+
+There are 3 variations to generate:
+		1. [0, n).
+	2. [min, max).
+	3. [min, max) with step.
+
+
+if len(params) == 1 considered that will be returned slice between 0 and n,
+where n is the first param, [0, n).
+if len(params) == 2 considered that will be returned slice between min and max,
+where min is the first param, max is the second, [min, max).
+if len(params) > 2 considered that will be returned slice between min and max with step,
+where min is the first param, max is the second, step is the third one, [min, max) with step,
+others params will be ignored
+
+
+## SequenceUsing
+
+SequenceUsing generates slice in range using creator function
+
+
+There are 3 variations to generate:
+		1. [0, n).
+	2. [min, max).
+	3. [min, max) with step.
+
+
+if len(params) == 1 considered that will be returned slice between 0 and n,
+where n is the first param, [0, n).
+if len(params) == 2 considered that will be returned slice between min and max,
+where min is the first param, max is the second, [min, max).
+if len(params) > 2 considered that will be returned slice between min and max with step,
+where min is the first param, max is the second, step is the third one, [min, max) with step,
+others params will be ignored
+
+
+## Shift
+
+Shift will return two values: the shifted value and the rest slice.
+
+
+## Shuffle
+
+Shuffle returns shuffled slice by your rand.Source
+
+
+## Sort
+
+Sort works similar to sort.SliceType(). However, unlike sort.SliceType the
+slice returned will be reallocated as to not modify the input slice.
+
+
+See Reverse() and AreSorted().
+
+
+## SortStableUsing
+
+SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the
+slice returned will be reallocated as to not modify the input slice.
+
+
+## SortUsing
+
+SortUsing works similar to sort.Slice. However, unlike sort.Slice the
+slice returned will be reallocated as to not modify the input slice.
+
+
+## Stddev
+
+Stddev is the standard deviation
+
+
+## Strings
+
+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:
+
+
+```go
+fmt.Sprintf("%v")
+
+```
+
+
+
+## SubSlice
+
+SubSlice will return the subSlice from start to end(excluded)
+
+
+Condition 1: If start < 0 or end < 0, nil is returned.
+Condition 2: If start >= end, nil is returned.
+Condition 3: Return all elements that exist in the range provided,
+if start or end is out of bounds, zero items will be placed.
+
+
+## Sum
+
+Sum is the sum of all of the elements.
+
+
+## Top
+
+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.
+
+
+## StringsUsing
+
+StringsUsing transforms each element to a string.
+
+
+## Unique
+
+Unique returns a new slice with all of the unique values.
+
+
+The items will be returned in a randomized order, even with the same input.
+
+
+The number of items returned may be the same as the input or less. It will
+never return zero items unless then input slice has zero items.
+
+
+A slice with zero elements is considered to be unique.
+
+
+See AreUnique().
+
+
+## Unshift
+
+Unshift adds one or more elements to the beginning of the slice
+and returns the new slice.
+
+
+## Values
+
+Values returns the values in the map.
+
+
+Due to Go's randomization of iterating maps the order is not deterministic.
+
+
+# FAQ
+
+## What are the requirements?
+
+`pie` supports many Go versions, all the way back to Go 1.8.
+
+## What are the goals of `pie`?
+
+1. **Type safety.** I never want to hit runtime bugs because I could pass in the
+wrong type, or perform an invalid type case out the other end.
+
+2. **Performance.** The functions need to be as fast as native Go
+implementations otherwise there's no point in this library existing.
+
+3. **Nil-safe.** All of the functions will happily accept nil and treat them as
+empty slices. Apart from less possible panics, it makes it easier to chain.
+
+4. **Immutable.** Functions never modify inputs, unlike some built-ins such as
+`sort.Strings`.
+
+## How do I contribute a function?
+
+Pull requests are always welcome.
+
+Here is a comprehensive list of steps to follow to add a new function:
+
+1. Create a new file in the `functions/` directory. The file should be named the
+same as the function. You must include documentation for your function.
+
+2. Update `functions/main.go` to register the new function by adding an entry to
+`Functions`. Make sure you choose the correct `For` value that is appropriate
+for your function.
+
+3. Run `go generate && go install && go generate ./...`. The first
+`generate` is to create the pie templates, `install` will update your binary for
+the annotations and the second `generate` will use the newly created templates
+to update the generated code for the internal types. If you encounter errors
+with your code you can safely rerun the command above.
+
+4. If you chose `ForAll` or `ForStructs`, then you must add unit tests to
+`pie/carpointers_test.go` and `pie/cars_test.go`.
+
+5. If you chose `ForAll`, `ForNumbersAndStrings` or `ForNumbers`, then you must
+add unit tests to `pie/float64s_test.go` and `pie/ints_test.go`.
+
+6. If you chose `ForAll` or `ForStrings`, then you must add unit tests to
+`pie/strings_test.go`.
+
+7. If you chose `ForMaps`, then you must add unit tests to `pie/currencies.go`.
+
+## Why is the emoji a slice of pizza instead of a pie?
+
+I wanted to pick a name for the project that was short and had an associated
+emoji. I liked pie, but then I found out that the pie emoji is not fully
+supported everywhere. I didn't want to change the name of the project to cake,
+but pizza pie still made sense. I'm not sure if I will change it back to a pie
+later.
+
+## How do I exclude generated files from code coverage?
+
+Go does not provide a way to exclude code coverage from specific files or lines
+with comments. However, you can remove the `_pie.go` files from the code
+coverage report before it is published:
+
+```bash
+go test -race -coverprofile=coverage.txt -covermode=atomic
+sed -i '/_pie\.go/d' ./coverage.txt
+```
+
+If you are running on macOS, you will need a slightly different syntax for
+`sed`:
+
+```bash
+sed -i '' /_pie\.go/d' ./coverage.txt
+```
diff --git a/v2/abs.go b/v2/abs.go
new file mode 100644
index 0000000..073061c
--- /dev/null
+++ b/v2/abs.go
@@ -0,0 +1,12 @@
+package pie
+
+import "golang.org/x/exp/constraints"
+
+// Abs returns the absolute value.
+func Abs[T constraints.Integer | constraints.Float](val T) T {
+	if val < 0 {
+		return -val
+	}
+
+	return val
+}
diff --git a/v2/all.go b/v2/all.go
new file mode 100644
index 0000000..09e6933
--- /dev/null
+++ b/v2/all.go
@@ -0,0 +1,15 @@
+package pie
+
+// All will return true if all callbacks return true. It follows the same logic
+// as the all() function in Python.
+//
+// If the list is empty then true is always returned.
+func All[T any](ss []T, fn func(value T) bool) bool {
+	for _, value := range ss {
+		if !fn(value) {
+			return false
+		}
+	}
+
+	return true
+}
diff --git a/v2/any.go b/v2/any.go
new file mode 100644
index 0000000..42292b9
--- /dev/null
+++ b/v2/any.go
@@ -0,0 +1,15 @@
+package pie
+
+// Any will return true if any callbacks return true. It follows the same logic
+// as the any() function in Python.
+//
+// If the list is empty then false is always returned.
+func Any[T any](ss []T, fn func(value T) bool) bool {
+	for _, value := range ss {
+		if fn(value) {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/v2/are_sorted.go b/v2/are_sorted.go
new file mode 100644
index 0000000..41e2388
--- /dev/null
+++ b/v2/are_sorted.go
@@ -0,0 +1,14 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+	"sort"
+)
+
+// AreSorted will return true if the slice is already sorted. It is a wrapper
+// for sort.SliceIsSorted.
+func AreSorted[T constraints.Ordered](ss []T) bool {
+	return sort.SliceIsSorted(ss, func(i, j int) bool {
+		return ss[i] < ss[j]
+	})
+}
diff --git a/v2/are_unique.go b/v2/are_unique.go
new file mode 100644
index 0000000..5a8df72
--- /dev/null
+++ b/v2/are_unique.go
@@ -0,0 +1,7 @@
+package pie
+
+// AreUnique will return true if the slice contains elements that are all
+// different (unique) from each other.
+func AreUnique[T comparable](ss []T) bool {
+	return len(Unique(ss)) == len(ss)
+}
diff --git a/v2/average.go b/v2/average.go
new file mode 100644
index 0000000..5961bd6
--- /dev/null
+++ b/v2/average.go
@@ -0,0 +1,13 @@
+package pie
+
+import "golang.org/x/exp/constraints"
+
+// Average is the average of all of the elements, or zero if there are no
+// elements.
+func Average[T constraints.Integer | constraints.Float](ss []T) float64 {
+	if l := len(ss); l > 0 {
+		return float64(Sum(ss)) / float64(l)
+	}
+
+	return 0
+}
diff --git a/v2/bottom.go b/v2/bottom.go
new file mode 100644
index 0000000..276bd6b
--- /dev/null
+++ b/v2/bottom.go
@@ -0,0 +1,17 @@
+package pie
+
+// Bottom will return n elements from bottom
+//
+// that means that elements is taken from the end of the slice
+// for this [1,2,3] slice with n == 2 will be returned [3,2]
+// if the slice has less elements then n that'll return all elements
+// if n < 0 it'll return empty slice.
+func Bottom[T any](ss []T, n int) (top []T) {
+	var lastIndex = len(ss) - 1
+	for i := lastIndex; i > -1 && n > 0; i-- {
+		top = append(top, ss[i])
+		n--
+	}
+
+	return
+}
diff --git a/v2/contains.go b/v2/contains.go
new file mode 100644
index 0000000..f772126
--- /dev/null
+++ b/v2/contains.go
@@ -0,0 +1,14 @@
+package pie
+
+// Contains returns true if the element exists in the slice.
+//
+// When using slices of pointers it will only compare by address, not value.
+func Contains[T comparable](ss []T, lookingFor T) bool {
+	for _, s := range ss {
+		if lookingFor == s {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/v2/diff.go b/v2/diff.go
new file mode 100644
index 0000000..6febbcb
--- /dev/null
+++ b/v2/diff.go
@@ -0,0 +1,42 @@
+package pie
+
+// Diff returns the elements that needs to be added or removed from the first
+// slice to have the same elements in the second slice.
+//
+// The order of elements is not taken into consideration, so the slices are
+// treated sets that allow duplicate items.
+//
+// The added and removed returned may be blank respectively, or contain upto as
+// many elements that exists in the largest slice.
+func Diff[T comparable](ss []T, against []T) (added, removed []T) {
+	// This is probably not the best way to do it. We do an O(n^2) between the
+	// slices to see which items are missing in each direction.
+
+	diffOneWay := func(ss1, ss2raw []T) (result []T) {
+		ss2 := make([]T, len(ss2raw))
+		copy(ss2, ss2raw)
+
+		for _, s := range ss1 {
+			found := false
+
+			for i, element := range ss2 {
+				if s == element {
+					ss2 = append(ss2[:i], ss2[i+1:]...)
+					found = true
+					break
+				}
+			}
+
+			if !found {
+				result = append(result, s)
+			}
+		}
+
+		return
+	}
+
+	removed = diffOneWay(ss, against)
+	added = diffOneWay(against, ss)
+
+	return
+}
diff --git a/v2/drop_top.go b/v2/drop_top.go
new file mode 100644
index 0000000..3aaeff8
--- /dev/null
+++ b/v2/drop_top.go
@@ -0,0 +1,17 @@
+package pie
+
+// DropTop will return the rest slice after dropping the top n elements
+// if the slice has less elements then n that'll return empty slice
+// if n < 0 it'll return empty slice.
+func DropTop[T any](ss []T, n int) (drop []T) {
+	if n < 0 || n >= len(ss) {
+		return
+	}
+
+	// Copy ss, to make sure no memory is overlapping between input and
+	// output. See issue #145.
+	drop = make([]T, len(ss)-n)
+	copy(drop, ss[n:])
+
+	return
+}
diff --git a/v2/drop_while.go b/v2/drop_while.go
new file mode 100644
index 0000000..0a04af4
--- /dev/null
+++ b/v2/drop_while.go
@@ -0,0 +1,16 @@
+package pie
+
+// Drop items from the slice while f(item) is true.
+// Afterwards, return every element until the slice is empty. It follows the
+// same logic as the dropwhile() function from itertools in Python.
+func DropWhile[T comparable](ss []T, f func(s T) bool) (ss2 []T) {
+	ss2 = make([]T, len(ss))
+	copy(ss2, ss)
+	for i, value := range ss2 {
+		if !f(value) {
+			return ss2[i:]
+		}
+	}
+
+	return []T{}
+}
diff --git a/v2/each.go b/v2/each.go
new file mode 100644
index 0000000..256e2e3
--- /dev/null
+++ b/v2/each.go
@@ -0,0 +1,24 @@
+package pie
+
+// Each is more condensed version of Transform that allows an action to happen
+// on each elements and pass the original slice on.
+//
+//   pie.Each(cars, func (car *Car) {
+//       fmt.Printf("Car color is: %s\n", car.Color)
+//   })
+//
+// Pie will not ensure immutability on items passed in so they can be
+// manipulated, if you choose to do it this way, for example:
+//
+//   // Set all car colors to Red.
+//   pie.Each(cars, func (car *Car) {
+//       car.Color = "Red"
+//   })
+//
+func Each[T any](ss []T, fn func(T)) []T {
+	for _, s := range ss {
+		fn(s)
+	}
+
+	return ss
+}
diff --git a/v2/equals.go b/v2/equals.go
new file mode 100644
index 0000000..14e4adc
--- /dev/null
+++ b/v2/equals.go
@@ -0,0 +1,23 @@
+package pie
+
+// Equals compare elements from the start to the end,
+//
+// if they are the same is considered the slices are equal if all elements are
+// the same is considered the slices are equal
+// if each slice == nil is considered that they're equal
+//
+// if element realizes Equals interface it uses that method, in other way uses
+// default compare
+func Equals[T comparable](ss []T, rhs []T) bool {
+	if len(ss) != len(rhs) {
+		return false
+	}
+
+	for i := range ss {
+		if ss[i] != rhs[i] {
+			return false
+		}
+	}
+
+	return true
+}
diff --git a/v2/filter.go b/v2/filter.go
new file mode 100644
index 0000000..1f831f9
--- /dev/null
+++ b/v2/filter.go
@@ -0,0 +1,15 @@
+package pie
+
+// Filter will return a new slice containing only the elements that return
+// true from the condition. The returned slice may contain zero elements (nil).
+//
+// FilterNot works in the opposite way of Filter.
+func Filter[T any](ss []T, condition func(T) bool) (ss2 []T) {
+	for _, s := range ss {
+		if condition(s) {
+			ss2 = append(ss2, s)
+		}
+	}
+
+	return
+}
diff --git a/v2/filter_not.go b/v2/filter_not.go
new file mode 100644
index 0000000..8a9e8e1
--- /dev/null
+++ b/v2/filter_not.go
@@ -0,0 +1,14 @@
+package pie
+
+// FilterNot works the same as Filter, with a negated condition. That is, it will
+// return a new slice only containing the elements that returned false from the
+// condition. The returned slice may contain zero elements (nil).
+func FilterNot[T any](ss []T, condition func(T) bool) (ss2 []T) {
+	for _, s := range ss {
+		if !condition(s) {
+			ss2 = append(ss2, s)
+		}
+	}
+
+	return
+}
diff --git a/v2/find_first_using.go b/v2/find_first_using.go
new file mode 100644
index 0000000..6194c3a
--- /dev/null
+++ b/v2/find_first_using.go
@@ -0,0 +1,16 @@
+package pie
+
+// FindFirstUsing will return the index of the first element when the callback
+// returns true or -1 if no element is found.
+// It follows the same logic as the findIndex() function in Javascript.
+//
+// If the list is empty then -1 is always returned.
+func FindFirstUsing[T any](ss []T, fn func(value T) bool) int {
+	for idx, value := range ss {
+		if fn(value) {
+			return idx
+		}
+	}
+
+	return -1
+}
diff --git a/v2/first_or.go b/v2/first_or.go
new file mode 100644
index 0000000..b43a2ef
--- /dev/null
+++ b/v2/first_or.go
@@ -0,0 +1,11 @@
+package pie
+
+// FirstOr returns the first element or a default value if there are no
+// elements.
+func FirstOr[T any](ss []T, defaultValue T) T {
+	if len(ss) == 0 {
+		return defaultValue
+	}
+
+	return ss[0]
+}
diff --git a/v2/float64.go b/v2/float64.go
new file mode 100644
index 0000000..5d565f3
--- /dev/null
+++ b/v2/float64.go
@@ -0,0 +1,15 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+	"strconv"
+)
+
+// Float64 transforms a value into a float64. This should only be used on slices
+// that resolve to strings that represent numbers. An invalid value will use
+// zero.
+func Float64[T constraints.Ordered](x T) float64 {
+	f, _ := strconv.ParseFloat(String(x), 64)
+
+	return f
+}
diff --git a/v2/float64s.go b/v2/float64s.go
new file mode 100644
index 0000000..8700096
--- /dev/null
+++ b/v2/float64s.go
@@ -0,0 +1,10 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+)
+
+// Float64s transforms each element to a float64.
+func Float64s[T constraints.Ordered](ss []T) []float64 {
+	return Map(ss, Float64[T])
+}
diff --git a/v2/go.mod b/v2/go.mod
new file mode 100644
index 0000000..c679064
--- /dev/null
+++ b/v2/go.mod
@@ -0,0 +1,14 @@
+module github.com/elliotchance/pie/v2
+
+go 1.18
+
+require (
+	github.com/stretchr/testify v1.7.1
+	golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705
+)
+
+require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+)
diff --git a/v2/go.sum b/v2/go.sum
new file mode 100644
index 0000000..7db84ec
--- /dev/null
+++ b/v2/go.sum
@@ -0,0 +1,14 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 h1:ba9YlqfDGTTQ5aZ2fwOoQ1hf32QySyQkR6ODGDzHlnE=
+golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/v2/group.go b/v2/group.go
new file mode 100644
index 0000000..ed0e3ca
--- /dev/null
+++ b/v2/group.go
@@ -0,0 +1,11 @@
+package pie
+
+// Group returns a map of the value with an individual count.
+func Group[T comparable](ss []T) map[T]int {
+	group := map[T]int{}
+	for _, n := range ss {
+		group[n]++
+	}
+
+	return group
+}
diff --git a/v2/insert.go b/v2/insert.go
new file mode 100644
index 0000000..67ab243
--- /dev/null
+++ b/v2/insert.go
@@ -0,0 +1,10 @@
+package pie
+
+// Insert a value at an index.
+func Insert[T any](ss []T, index int, values ...T) []T {
+	if index >= len(ss) {
+		return append(ss, values...)
+	}
+
+	return append(ss[:index], append(values, ss[index:]...)...)
+}
diff --git a/v2/int.go b/v2/int.go
new file mode 100644
index 0000000..f8af99f
--- /dev/null
+++ b/v2/int.go
@@ -0,0 +1,12 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+)
+
+// Int transforms a value into an int. This should only be used on slices
+// that resolve to strings that represent numbers. An invalid value will use
+// zero and fractional values will be truncated.
+func Int[T constraints.Ordered](x T) int {
+	return int(Float64(x))
+}
diff --git a/v2/intersect.go b/v2/intersect.go
new file mode 100644
index 0000000..37c368c
--- /dev/null
+++ b/v2/intersect.go
@@ -0,0 +1,36 @@
+package pie
+
+// Intersect returns items that exist in all lists.
+//
+// It returns slice without any duplicates.
+// If zero slice arguments are provided, then nil is returned.
+func Intersect[T comparable](ss []T, slices ...[]T) (ss2 []T) {
+	if slices == nil {
+		return nil
+	}
+
+	var uniqs = make([]map[T]struct{}, len(slices))
+	for i := 0; i < len(slices); i++ {
+		m := make(map[T]struct{})
+		for _, el := range slices[i] {
+			m[el] = struct{}{}
+		}
+		uniqs[i] = m
+	}
+
+	var containsInAll = false
+	for _, el := range Unique(ss) {
+		for _, u := range uniqs {
+			if _, exists := u[el]; !exists {
+				containsInAll = false
+				break
+			}
+			containsInAll = true
+		}
+		if containsInAll {
+			ss2 = append(ss2, el)
+		}
+	}
+
+	return
+}
diff --git a/v2/ints.go b/v2/ints.go
new file mode 100644
index 0000000..c590c5f
--- /dev/null
+++ b/v2/ints.go
@@ -0,0 +1,10 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+)
+
+// Ints transforms each element to an integer.
+func Ints[T constraints.Ordered](ss []T) []int {
+	return Map(ss, Int[T])
+}
diff --git a/v2/join.go b/v2/join.go
new file mode 100644
index 0000000..4144afe
--- /dev/null
+++ b/v2/join.go
@@ -0,0 +1,16 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+	"strings"
+)
+
+// Join returns a string from joining each of the elements.
+func Join[T constraints.Ordered](ss []T, glue string) (s string) {
+	parts := make([]string, len(ss))
+	for i, element := range ss {
+		parts[i] = String(element)
+	}
+
+	return strings.Join(parts, glue)
+}
diff --git a/v2/json_bytes.go b/v2/json_bytes.go
new file mode 100644
index 0000000..4bf7b9d
--- /dev/null
+++ b/v2/json_bytes.go
@@ -0,0 +1,21 @@
+package pie
+
+import (
+	"encoding/json"
+	"golang.org/x/exp/constraints"
+)
+
+// JSONBytes returns the JSON encoded array as bytes.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array.
+func JSONBytes[T constraints.Ordered](ss []T) []byte {
+	if ss == nil {
+		return []byte("[]")
+	}
+
+	// An error should not be possible.
+	data, _ := json.Marshal(ss)
+
+	return data
+}
diff --git a/v2/json_bytes_indent.go b/v2/json_bytes_indent.go
new file mode 100644
index 0000000..b79a985
--- /dev/null
+++ b/v2/json_bytes_indent.go
@@ -0,0 +1,22 @@
+package pie
+
+import (
+	"encoding/json"
+	"golang.org/x/exp/constraints"
+)
+
+// JSONBytesIndent returns the JSON encoded array as bytes with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func JSONBytesIndent[T constraints.Ordered](ss []T, prefix, indent string) []byte {
+	if ss == nil {
+		return []byte("[]")
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return data
+}
diff --git a/v2/json_string.go b/v2/json_string.go
new file mode 100644
index 0000000..f9b5f37
--- /dev/null
+++ b/v2/json_string.go
@@ -0,0 +1,21 @@
+package pie
+
+import (
+	"encoding/json"
+	"golang.org/x/exp/constraints"
+)
+
+// JSONString returns the JSON encoded array as a string.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array.
+func JSONString[T constraints.Ordered](ss []T) string {
+	if ss == nil {
+		return "[]"
+	}
+
+	// An error should not be possible.
+	data, _ := json.Marshal(ss)
+
+	return string(data)
+}
diff --git a/v2/json_string_indent.go b/v2/json_string_indent.go
new file mode 100644
index 0000000..10d8a09
--- /dev/null
+++ b/v2/json_string_indent.go
@@ -0,0 +1,22 @@
+package pie
+
+import (
+	"encoding/json"
+	"golang.org/x/exp/constraints"
+)
+
+// JSONStringIndent returns the JSON encoded array as a string with indent applied.
+//
+// One important thing to note is that it will treat a nil slice as an empty
+// slice to ensure that the JSON value return is always an array. See
+// json.MarshalIndent for details.
+func JSONStringIndent[T constraints.Ordered](ss []T, prefix, indent string) string {
+	if ss == nil {
+		return "[]"
+	}
+
+	// An error should not be possible.
+	data, _ := json.MarshalIndent(ss, prefix, indent)
+
+	return string(data)
+}
diff --git a/v2/keys.go b/v2/keys.go
new file mode 100644
index 0000000..0e22f44
--- /dev/null
+++ b/v2/keys.go
@@ -0,0 +1,21 @@
+package pie
+
+// Keys returns the keys in the map. All of the items will be unique.
+//
+// Due to Go's randomization of iterating maps the order is not deterministic.
+func Keys[K comparable, V any](m map[K]V) []K {
+	// Avoid allocation
+	l := len(m)
+	if l == 0 {
+		return nil
+	}
+
+	i := 0
+	keys := make([]K, len(m))
+	for key := range m {
+		keys[i] = key
+		i++
+	}
+
+	return keys
+}
diff --git a/v2/last_or.go b/v2/last_or.go
new file mode 100644
index 0000000..4050963
--- /dev/null
+++ b/v2/last_or.go
@@ -0,0 +1,10 @@
+package pie
+
+// LastOr returns the last element or a default value if there are no elements.
+func LastOr[T any](ss []T, defaultValue T) T {
+	if len(ss) == 0 {
+		return defaultValue
+	}
+
+	return ss[len(ss)-1]
+}
diff --git a/v2/map.go b/v2/map.go
new file mode 100644
index 0000000..5a5ff03
--- /dev/null
+++ b/v2/map.go
@@ -0,0 +1,20 @@
+package pie
+
+// Map will return a new slice where each element has been mapped (transformed).
+// The number of elements returned will always be the same as the input.
+//
+// Be careful when using this with slices of pointers. If you modify the input
+// value it will affect the original slice. Be sure to return a new allocated
+// object or deep copy the existing one.
+func Map[T any, U any](ss []T, fn func(T) U) (ss2 []U) {
+	if ss == nil {
+		return nil
+	}
+
+	ss2 = make([]U, len(ss))
+	for i, s := range ss {
+		ss2[i] = fn(s)
+	}
+
+	return
+}
diff --git a/v2/max.go b/v2/max.go
new file mode 100644
index 0000000..b19646b
--- /dev/null
+++ b/v2/max.go
@@ -0,0 +1,21 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+)
+
+// Max is the maximum value, or zero.
+func Max[T constraints.Ordered](ss []T) (min T) {
+	if len(ss) == 0 {
+		return
+	}
+
+	min = ss[0]
+	for _, s := range ss {
+		if s > min {
+			min = s
+		}
+	}
+
+	return
+}
diff --git a/v2/median.go b/v2/median.go
new file mode 100644
index 0000000..d5570ea
--- /dev/null
+++ b/v2/median.go
@@ -0,0 +1,69 @@
+package pie
+
+import "golang.org/x/exp/constraints"
+
+// Median returns the value separating the higher half from the lower half of a
+// data sample.
+//
+// Zero is returned if there are no elements in the slice.
+//
+// If the number of elements is even, then the ElementType mean of the two
+// "median values" is returned.
+func Median[T constraints.Integer | constraints.Float](ss []T) T {
+	n := len(ss)
+	if n == 0 {
+		return 0
+	}
+
+	if n == 1 {
+		return ss[0]
+	}
+
+	// This implementation aims at linear time O(n) on average.
+	// It uses the same idea as QuickSort, but makes only 1 recursive
+	// call instead of 2. See also Quickselect.
+
+	work := make([]T, len(ss))
+	copy(work, ss)
+
+	limit1, limit2 := n/2, n/2+1
+	if n%2 == 0 {
+		limit1, limit2 = n/2-1, n/2+1
+	}
+
+	var rec func(a, b int)
+	rec = func(a, b int) {
+		if b-a <= 1 {
+			return
+		}
+		ipivot := (a + b) / 2
+		pivot := work[ipivot]
+		work[a], work[ipivot] = work[ipivot], work[a]
+		j := a
+		k := b
+		for j+1 < k {
+			if work[j+1] < pivot {
+				work[j+1], work[j] = work[j], work[j+1]
+				j++
+			} else {
+				work[j+1], work[k-1] = work[k-1], work[j+1]
+				k--
+			}
+		}
+		// 1 or 0 recursive calls
+		if j > limit1 {
+			rec(a, j)
+		}
+		if j+1 < limit2 {
+			rec(j+1, b)
+		}
+	}
+
+	rec(0, len(work))
+
+	if n%2 == 1 {
+		return work[n/2]
+	} else {
+		return (work[n/2-1] + work[n/2]) / 2
+	}
+}
diff --git a/v2/min.go b/v2/min.go
new file mode 100644
index 0000000..a5ded29
--- /dev/null
+++ b/v2/min.go
@@ -0,0 +1,21 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+)
+
+// Min is the minimum value, or zero.
+func Min[T constraints.Ordered](ss []T) (min T) {
+	if len(ss) == 0 {
+		return
+	}
+
+	min = ss[0]
+	for _, s := range ss {
+		if s < min {
+			min = s
+		}
+	}
+
+	return
+}
diff --git a/v2/mode.go b/v2/mode.go
new file mode 100644
index 0000000..34f77f7
--- /dev/null
+++ b/v2/mode.go
@@ -0,0 +1,32 @@
+package pie
+
+// Mode returns a new slice containing the most frequently occuring values.
+//
+// The number of items returned may be the same as the input or less. It will
+// never return zero items unless the input slice has zero items.
+func Mode[T comparable](ss []T) []T {
+	if len(ss) == 0 {
+		return nil
+	}
+
+	values := make(map[T]int)
+	for _, s := range ss {
+		values[s]++
+	}
+
+	var maxFrequency int
+	for _, v := range values {
+		if v > maxFrequency {
+			maxFrequency = v
+		}
+	}
+
+	var maxValues []T
+	for k, v := range values {
+		if v == maxFrequency {
+			maxValues = append(maxValues, k)
+		}
+	}
+
+	return maxValues
+}
diff --git a/v2/of.go b/v2/of.go
new file mode 100644
index 0000000..b44b106
--- /dev/null
+++ b/v2/of.go
@@ -0,0 +1,102 @@
+package pie
+
+import (
+	"context"
+	"math/rand"
+)
+
+// Of encapsulates a slice to be used in multiple chained operations.
+func Of[T any](ss []T) OfSlice[T] {
+	return OfSlice[T]{ss}
+}
+
+// OfSlice provides the proxy methods that operate on slices. If the last method
+// in the chain does not return a single value, you can access the Result to get
+// final slice.
+type OfSlice[T any] struct {
+	Result []T
+}
+
+func (o OfSlice[T]) All(fn func(value T) bool) bool {
+	return All(o.Result, fn)
+}
+
+func (o OfSlice[T]) Any(fn func(value T) bool) bool {
+	return Any(o.Result, fn)
+}
+
+func (o OfSlice[T]) Bottom(n int) OfSlice[T] {
+	return OfSlice[T]{Bottom(o.Result, n)}
+}
+
+func (o OfSlice[T]) DropTop(n int) OfSlice[T] {
+	return OfSlice[T]{DropTop(o.Result, n)}
+}
+
+func (o OfSlice[T]) Each(fn func(T)) OfSlice[T] {
+	return OfSlice[T]{Each(o.Result, fn)}
+}
+
+func (o OfSlice[T]) Filter(condition func(T) bool) OfSlice[T] {
+	return OfSlice[T]{Filter(o.Result, condition)}
+}
+
+func (o OfSlice[T]) FilterNot(condition func(T) bool) OfSlice[T] {
+	return OfSlice[T]{FilterNot(o.Result, condition)}
+}
+
+func (o OfSlice[T]) FindFirstUsing(fn func(value T) bool) int {
+	return FindFirstUsing(o.Result, fn)
+}
+
+func (o OfSlice[T]) FirstOr(defaultValue T) T {
+	return FirstOr(o.Result, defaultValue)
+}
+
+func (o OfSlice[T]) Insert(index int, values ...T) OfSlice[T] {
+	return OfSlice[T]{Insert(o.Result, index, values...)}
+}
+
+func (o OfSlice[T]) LastOr(defaultValue T) T {
+	return LastOr(o.Result, defaultValue)
+}
+
+func (o OfSlice[T]) Map(fn func(T) T) OfSlice[T] {
+	return OfSlice[T]{Map(o.Result, fn)}
+}
+
+func (o OfSlice[T]) Reverse() OfSlice[T] {
+	return OfSlice[T]{Reverse(o.Result)}
+}
+
+func (o OfSlice[T]) Send(ctx context.Context, ch chan<- T) OfSlice[T] {
+	return OfSlice[T]{Send(ctx, o.Result, ch)}
+}
+
+func (o OfSlice[T]) SequenceUsing(creator func(int) T, params ...int) OfSlice[T] {
+	return OfSlice[T]{SequenceUsing(o.Result, creator, params...)}
+}
+
+func (o OfSlice[T]) Shuffle(source rand.Source) OfSlice[T] {
+	return OfSlice[T]{Shuffle(o.Result, source)}
+}
+
+func (o OfSlice[T]) SortUsing(less func(a, b T) bool) OfSlice[T] {
+	return OfSlice[T]{SortUsing(o.Result, less)}
+}
+
+func (o OfSlice[T]) StringsUsing(transform func(T) string) []string {
+	return StringsUsing(o.Result, transform)
+}
+
+func (o OfSlice[T]) SubSlice(start int, end int) OfSlice[T] {
+	return OfSlice[T]{SubSlice(o.Result, start, end)}
+}
+
+func (o OfSlice[T]) Top(n int) OfSlice[T] {
+	return OfSlice[T]{Top(o.Result, n)}
+}
+
+func (o OfSlice[T]) Unshift(elements ...T) OfSlice[T] {
+	return OfSlice[T]{Unshift(o.Result, elements...)}
+}
diff --git a/v2/of_numeric.go b/v2/of_numeric.go
new file mode 100644
index 0000000..d314866
--- /dev/null
+++ b/v2/of_numeric.go
@@ -0,0 +1,225 @@
+package pie
+
+import (
+	"context"
+	"golang.org/x/exp/constraints"
+	"math/rand"
+)
+
+// OfNumeric encapsulates a slice to be used in multiple chained operations.
+// OfNumeric requires that elements be numerical for certain operations to be
+// performed.
+func OfNumeric[T constraints.Integer | constraints.Float](ss []T) OfNumericSlice[T] {
+	return OfNumericSlice[T]{ss}
+}
+
+// OfNumericSlice provides the proxy methods that operate on slices. If the last
+// method in the chain does not return a single value, you can access the Result
+// to get final slice.
+type OfNumericSlice[T constraints.Integer | constraints.Float] struct {
+	Result []T
+}
+
+func (o OfNumericSlice[T]) All(fn func(value T) bool) bool {
+	return All(o.Result, fn)
+}
+
+func (o OfNumericSlice[T]) Any(fn func(value T) bool) bool {
+	return Any(o.Result, fn)
+}
+
+func (o OfNumericSlice[T]) AreSorted() bool {
+	return AreSorted(o.Result)
+}
+
+func (o OfNumericSlice[T]) AreUnique() bool {
+	return AreUnique(o.Result)
+}
+
+func (o OfNumericSlice[T]) Average() float64 {
+	return Average(o.Result)
+}
+
+func (o OfNumericSlice[T]) Bottom(n int) OfNumericSlice[T] {
+	return OfNumericSlice[T]{Bottom(o.Result, n)}
+}
+
+func (o OfNumericSlice[T]) Contains(lookingFor T) bool {
+	return Contains(o.Result, lookingFor)
+}
+
+func (o OfNumericSlice[T]) Diff(against []T) ([]T, []T) {
+	return Diff(o.Result, against)
+}
+
+func (o OfNumericSlice[T]) DropTop(n int) OfNumericSlice[T] {
+	return OfNumericSlice[T]{DropTop(o.Result, n)}
+}
+
+func (o OfNumericSlice[T]) DropWhile(f func(s T) bool) OfNumericSlice[T] {
+	return OfNumericSlice[T]{DropWhile(o.Result, f)}
+}
+
+func (o OfNumericSlice[T]) Each(fn func(T)) OfNumericSlice[T] {
+	return OfNumericSlice[T]{Each(o.Result, fn)}
+}
+
+func (o OfNumericSlice[T]) Equals(rhs []T) bool {
+	return Equals(o.Result, rhs)
+}
+
+func (o OfNumericSlice[T]) Filter(condition func(T) bool) OfNumericSlice[T] {
+	return OfNumericSlice[T]{Filter(o.Result, condition)}
+}
+
+func (o OfNumericSlice[T]) FilterNot(condition func(T) bool) OfNumericSlice[T] {
+	return OfNumericSlice[T]{FilterNot(o.Result, condition)}
+}
+
+func (o OfNumericSlice[T]) FindFirstUsing(fn func(value T) bool) int {
+	return FindFirstUsing(o.Result, fn)
+}
+
+func (o OfNumericSlice[T]) FirstOr(defaultValue T) T {
+	return FirstOr(o.Result, defaultValue)
+}
+
+func (o OfNumericSlice[T]) Float64s() []float64 {
+	return Float64s(o.Result)
+}
+
+func (o OfNumericSlice[T]) Group() map[T]int {
+	return Group(o.Result)
+}
+
+func (o OfNumericSlice[T]) Insert(index int, values ...T) OfNumericSlice[T] {
+	return OfNumericSlice[T]{Insert(o.Result, index, values...)}
+}
+
+func (o OfNumericSlice[T]) Intersect(slices ...[]T) OfNumericSlice[T] {
+	return OfNumericSlice[T]{Intersect(o.Result, slices...)}
+}
+
+func (o OfNumericSlice[T]) Ints() []int {
+	return Ints(o.Result)
+}
+
+func (o OfNumericSlice[T]) Join(glue string) string {
+	return Join(o.Result, glue)
+}
+
+func (o OfNumericSlice[T]) JSONBytes() []byte {
+	return JSONBytes(o.Result)
+}
+
+func (o OfNumericSlice[T]) JSONBytesIndent(prefix, indent string) []byte {
+	return JSONBytesIndent(o.Result, prefix, indent)
+}
+
+func (o OfNumericSlice[T]) JSONString() string {
+	return JSONString(o.Result)
+}
+
+func (o OfNumericSlice[T]) JSONStringIndent(prefix, indent string) string {
+	return JSONStringIndent(o.Result, prefix, indent)
+}
+
+func (o OfNumericSlice[T]) LastOr(defaultValue T) T {
+	return LastOr(o.Result, defaultValue)
+}
+
+func (o OfNumericSlice[T]) Map(fn func(T) T) OfNumericSlice[T] {
+	return OfNumericSlice[T]{Map(o.Result, fn)}
+}
+
+func (o OfNumericSlice[T]) Max() T {
+	return Max(o.Result)
+}
+
+func (o OfNumericSlice[T]) Median() T {
+	return Median(o.Result)
+}
+
+func (o OfNumericSlice[T]) Min() T {
+	return Min(o.Result)
+}
+
+func (o OfNumericSlice[T]) Mode() OfNumericSlice[T] {
+	return OfNumericSlice[T]{Mode(o.Result)}
+}
+
+func (o OfNumericSlice[T]) Product() T {
+	return Product(o.Result)
+}
+
+func (o OfNumericSlice[T]) Random(source rand.Source) T {
+	return Random(o.Result, source)
+}
+
+func (o OfNumericSlice[T]) Reduce(reducer func(T, T) T) T {
+	return Reduce(o.Result, reducer)
+}
+
+func (o OfNumericSlice[T]) Reverse() OfNumericSlice[T] {
+	return OfNumericSlice[T]{Reverse(o.Result)}
+}
+
+func (o OfNumericSlice[T]) Send(ctx context.Context, ch chan<- T) OfNumericSlice[T] {
+	return OfNumericSlice[T]{Send(ctx, o.Result, ch)}
+}
+
+func (o OfNumericSlice[T]) Sequence(params ...int) OfNumericSlice[T] {
+	return OfNumericSlice[T]{Sequence(o.Result, params...)}
+}
+
+func (o OfNumericSlice[T]) SequenceUsing(creator func(int) T, params ...int) OfNumericSlice[T] {
+	return OfNumericSlice[T]{SequenceUsing(o.Result, creator, params...)}
+}
+
+func (o OfNumericSlice[T]) Shuffle(source rand.Source) OfNumericSlice[T] {
+	return OfNumericSlice[T]{Shuffle(o.Result, source)}
+}
+
+func (o OfNumericSlice[T]) Sort() OfNumericSlice[T] {
+	return OfNumericSlice[T]{Sort(o.Result)}
+}
+
+func (o OfNumericSlice[T]) SortStableUsing(less func(a, b T) bool) OfNumericSlice[T] {
+	return OfNumericSlice[T]{SortStableUsing(o.Result, less)}
+}
+
+func (o OfNumericSlice[T]) SortUsing(less func(a, b T) bool) OfNumericSlice[T] {
+	return OfNumericSlice[T]{SortUsing(o.Result, less)}
+}
+
+func (o OfNumericSlice[T]) Stddev() float64 {
+	return Stddev(o.Result)
+}
+
+func (o OfNumericSlice[T]) Strings() []string {
+	return Strings(o.Result)
+}
+
+func (o OfNumericSlice[T]) StringsUsing(transform func(T) string) []string {
+	return StringsUsing(o.Result, transform)
+}
+
+func (o OfNumericSlice[T]) SubSlice(start int, end int) OfNumericSlice[T] {
+	return OfNumericSlice[T]{SubSlice(o.Result, start, end)}
+}
+
+func (o OfNumericSlice[T]) Sum() T {
+	return Sum(o.Result)
+}
+
+func (o OfNumericSlice[T]) Top(n int) OfNumericSlice[T] {
+	return OfNumericSlice[T]{Top(o.Result, n)}
+}
+
+func (o OfNumericSlice[T]) Unique() OfNumericSlice[T] {
+	return OfNumericSlice[T]{Unique(o.Result)}
+}
+
+func (o OfNumericSlice[T]) Unshift(elements ...T) OfNumericSlice[T] {
+	return OfNumericSlice[T]{Unshift(o.Result, elements...)}
+}
diff --git a/v2/of_ordered.go b/v2/of_ordered.go
new file mode 100644
index 0000000..5d20fb3
--- /dev/null
+++ b/v2/of_ordered.go
@@ -0,0 +1,193 @@
+package pie
+
+import (
+	"context"
+	"golang.org/x/exp/constraints"
+	"math/rand"
+)
+
+// OfOrdered encapsulates a slice to be used in multiple chained operations.
+// OfOrdered requires that elements be numerical or a string for certain
+// operations to be performed.
+func OfOrdered[T constraints.Ordered](ss []T) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{ss}
+}
+
+// OfOrderedSlice provides the proxy methods that operate on slices. If the last
+// method in the chain does not return a single value, you can access the Result
+// to get final slice.
+type OfOrderedSlice[T constraints.Ordered] struct {
+	Result []T
+}
+
+func (o OfOrderedSlice[T]) All(fn func(value T) bool) bool {
+	return All(o.Result, fn)
+}
+
+func (o OfOrderedSlice[T]) Any(fn func(value T) bool) bool {
+	return Any(o.Result, fn)
+}
+
+func (o OfOrderedSlice[T]) AreSorted() bool {
+	return AreSorted(o.Result)
+}
+
+func (o OfOrderedSlice[T]) AreUnique() bool {
+	return AreUnique(o.Result)
+}
+
+func (o OfOrderedSlice[T]) Bottom(n int) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Bottom(o.Result, n)}
+}
+
+func (o OfOrderedSlice[T]) Contains(lookingFor T) bool {
+	return Contains(o.Result, lookingFor)
+}
+
+func (o OfOrderedSlice[T]) Diff(against []T) ([]T, []T) {
+	return Diff(o.Result, against)
+}
+
+func (o OfOrderedSlice[T]) DropTop(n int) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{DropTop(o.Result, n)}
+}
+
+func (o OfOrderedSlice[T]) DropWhile(f func(s T) bool) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{DropWhile(o.Result, f)}
+}
+
+func (o OfOrderedSlice[T]) Each(fn func(T)) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Each(o.Result, fn)}
+}
+
+func (o OfOrderedSlice[T]) Equals(rhs []T) bool {
+	return Equals(o.Result, rhs)
+}
+
+func (o OfOrderedSlice[T]) Filter(condition func(T) bool) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Filter(o.Result, condition)}
+}
+
+func (o OfOrderedSlice[T]) FilterNot(condition func(T) bool) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{FilterNot(o.Result, condition)}
+}
+
+func (o OfOrderedSlice[T]) FindFirstUsing(fn func(value T) bool) int {
+	return FindFirstUsing(o.Result, fn)
+}
+
+func (o OfOrderedSlice[T]) FirstOr(defaultValue T) T {
+	return FirstOr(o.Result, defaultValue)
+}
+
+func (o OfOrderedSlice[T]) Float64s() []float64 {
+	return Float64s(o.Result)
+}
+
+func (o OfOrderedSlice[T]) Group() map[T]int {
+	return Group(o.Result)
+}
+
+func (o OfOrderedSlice[T]) Insert(index int, values ...T) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Insert(o.Result, index, values...)}
+}
+
+func (o OfOrderedSlice[T]) Intersect(slices ...[]T) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Intersect(o.Result, slices...)}
+}
+
+func (o OfOrderedSlice[T]) Ints() []int {
+	return Ints(o.Result)
+}
+
+func (o OfOrderedSlice[T]) Join(glue string) string {
+	return Join(o.Result, glue)
+}
+
+func (o OfOrderedSlice[T]) JSONBytes() []byte {
+	return JSONBytes(o.Result)
+}
+
+func (o OfOrderedSlice[T]) JSONBytesIndent(prefix, indent string) []byte {
+	return JSONBytesIndent(o.Result, prefix, indent)
+}
+
+func (o OfOrderedSlice[T]) JSONString() string {
+	return JSONString(o.Result)
+}
+
+func (o OfOrderedSlice[T]) JSONStringIndent(prefix, indent string) string {
+	return JSONStringIndent(o.Result, prefix, indent)
+}
+
+func (o OfOrderedSlice[T]) LastOr(defaultValue T) T {
+	return LastOr(o.Result, defaultValue)
+}
+
+func (o OfOrderedSlice[T]) Map(fn func(T) T) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Map(o.Result, fn)}
+}
+
+func (o OfOrderedSlice[T]) Max() T {
+	return Max(o.Result)
+}
+
+func (o OfOrderedSlice[T]) Min() T {
+	return Min(o.Result)
+}
+
+func (o OfOrderedSlice[T]) Mode() OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Mode(o.Result)}
+}
+
+func (o OfOrderedSlice[T]) Reverse() OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Reverse(o.Result)}
+}
+
+func (o OfOrderedSlice[T]) Send(ctx context.Context, ch chan<- T) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Send(ctx, o.Result, ch)}
+}
+
+func (o OfOrderedSlice[T]) SequenceUsing(creator func(int) T, params ...int) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{SequenceUsing(o.Result, creator, params...)}
+}
+
+func (o OfOrderedSlice[T]) Shuffle(source rand.Source) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Shuffle(o.Result, source)}
+}
+
+func (o OfOrderedSlice[T]) Sort() OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Sort(o.Result)}
+}
+
+func (o OfOrderedSlice[T]) SortStableUsing(less func(a, b T) bool) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{SortStableUsing(o.Result, less)}
+}
+
+func (o OfOrderedSlice[T]) SortUsing(less func(a, b T) bool) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{SortUsing(o.Result, less)}
+}
+
+func (o OfOrderedSlice[T]) Strings() []string {
+	return Strings(o.Result)
+}
+
+func (o OfOrderedSlice[T]) StringsUsing(transform func(T) string) []string {
+	return StringsUsing(o.Result, transform)
+}
+
+func (o OfOrderedSlice[T]) SubSlice(start int, end int) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{SubSlice(o.Result, start, end)}
+}
+
+func (o OfOrderedSlice[T]) Top(n int) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Top(o.Result, n)}
+}
+
+func (o OfOrderedSlice[T]) Unique() OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Unique(o.Result)}
+}
+
+func (o OfOrderedSlice[T]) Unshift(elements ...T) OfOrderedSlice[T] {
+	return OfOrderedSlice[T]{Unshift(o.Result, elements...)}
+}
diff --git a/v2/pop.go b/v2/pop.go
new file mode 100644
index 0000000..00f8b31
--- /dev/null
+++ b/v2/pop.go
@@ -0,0 +1,22 @@
+package pie
+
+// Pop the first element of the slice
+//
+// Usage Example:
+//
+//   type knownGreetings []string
+//   greetings := knownGreetings{"ciao", "hello", "hola"}
+//   for greeting := greetings.Pop(); greeting != nil; greeting = greetings.Pop() {
+//       fmt.Println(*greeting)
+//   }
+//
+func Pop[T any](ss *[]T) (popped *T) {
+	if len(*ss) == 0 {
+		return
+	}
+
+	popped = &(*ss)[0]
+	*ss = (*ss)[1:]
+
+	return
+}
diff --git a/v2/product.go b/v2/product.go
new file mode 100644
index 0000000..c117252
--- /dev/null
+++ b/v2/product.go
@@ -0,0 +1,17 @@
+package pie
+
+import "golang.org/x/exp/constraints"
+
+// Product is the product of all of the elements.
+func Product[T constraints.Integer | constraints.Float](ss []T) (product T) {
+	if len(ss) == 0 {
+		return
+	}
+
+	product = ss[0]
+	for _, s := range ss[1:] {
+		product *= s
+	}
+
+	return
+}
diff --git a/v2/random.go b/v2/random.go
new file mode 100644
index 0000000..7ad68b5
--- /dev/null
+++ b/v2/random.go
@@ -0,0 +1,25 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+	"math/rand"
+)
+
+// Random returns a random element by your rand.Source, or zero.
+func Random[T constraints.Integer | constraints.Float](ss []T, source rand.Source) T {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 1 {
+		return 0
+	}
+
+	if n < 2 {
+		return ss[0]
+	}
+
+	rnd := rand.New(source)
+	i := rnd.Intn(n)
+
+	return ss[i]
+}
diff --git a/v2/reduce.go b/v2/reduce.go
new file mode 100644
index 0000000..da88949
--- /dev/null
+++ b/v2/reduce.go
@@ -0,0 +1,21 @@
+package pie
+
+// Reduce continually applies the provided function
+// over the slice. Reducing the elements to a single value.
+//
+// Returns a zero value of T if there are no elements in the slice. It will
+// panic if the reducer is nil and the slice has more than one element (required
+// to invoke reduce). Otherwise returns result of applying reducer from left to
+// right.
+func Reduce[T any](ss []T, reducer func(T, T) T) (el T) {
+	if len(ss) == 0 {
+		return
+	}
+
+	el = ss[0]
+	for _, s := range ss[1:] {
+		el = reducer(el, s)
+	}
+
+	return
+}
diff --git a/v2/reverse.go b/v2/reverse.go
new file mode 100644
index 0000000..fffab1c
--- /dev/null
+++ b/v2/reverse.go
@@ -0,0 +1,21 @@
+package pie
+
+// Reverse returns a new copy of the slice with the elements ordered in reverse.
+// This is useful when combined with Sort to get a descending sort order:
+//
+//   ss.Sort().Reverse()
+//
+func Reverse[T any](ss []T) []T {
+	// Avoid the allocation. If there is one element or less it is already
+	// reversed.
+	if len(ss) < 2 {
+		return ss
+	}
+
+	sorted := make([]T, len(ss))
+	for i := 0; i < len(ss); i++ {
+		sorted[i] = ss[len(ss)-i-1]
+	}
+
+	return sorted
+}
diff --git a/v2/send.go b/v2/send.go
new file mode 100644
index 0000000..20ee254
--- /dev/null
+++ b/v2/send.go
@@ -0,0 +1,24 @@
+package pie
+
+import (
+	"context"
+)
+
+// Send sends elements to channel
+// in normal act it sends all elements but if func canceled it can be less
+//
+// it locks execution of gorutine
+// it doesn't close channel after work
+// returns sent elements if len(this) != len(old) considered func was canceled
+func Send[T any](ctx context.Context, ss []T, ch chan<- T) []T {
+	for i, s := range ss {
+		select {
+		case <-ctx.Done():
+			return ss[:i]
+		default:
+			ch <- s
+		}
+	}
+
+	return ss
+}
diff --git a/v2/sequence.go b/v2/sequence.go
new file mode 100644
index 0000000..f84ce7b
--- /dev/null
+++ b/v2/sequence.go
@@ -0,0 +1,25 @@
+package pie
+
+import "golang.org/x/exp/constraints"
+
+// Sequence generates all numbers in range or returns nil if params invalid
+//
+// There are 3 variations to generate:
+// 		1. [0, n).
+//		2. [min, max).
+//		3. [min, max) with step.
+//
+// if len(params) == 1 considered that will be returned slice between 0 and n,
+// where n is the first param, [0, n).
+// if len(params) == 2 considered that will be returned slice between min and max,
+// where min is the first param, max is the second, [min, max).
+// if len(params) > 2 considered that will be returned slice between min and max with step,
+// where min is the first param, max is the second, step is the third one, [min, max) with step,
+// others params will be ignored
+func Sequence[T constraints.Integer | constraints.Float](ss []T, params ...int) []T {
+	var creator = func(i int) T {
+		return T(i)
+	}
+
+	return SequenceUsing(ss, creator, params...)
+}
diff --git a/v2/sequence_using.go b/v2/sequence_using.go
new file mode 100644
index 0000000..1a5e930
--- /dev/null
+++ b/v2/sequence_using.go
@@ -0,0 +1,44 @@
+package pie
+
+import "math"
+
+// SequenceUsing generates slice in range using creator function
+//
+// There are 3 variations to generate:
+// 		1. [0, n).
+//		2. [min, max).
+//		3. [min, max) with step.
+//
+// if len(params) == 1 considered that will be returned slice between 0 and n,
+// where n is the first param, [0, n).
+// if len(params) == 2 considered that will be returned slice between min and max,
+// where min is the first param, max is the second, [min, max).
+// if len(params) > 2 considered that will be returned slice between min and max with step,
+// where min is the first param, max is the second, step is the third one, [min, max) with step,
+// others params will be ignored
+func SequenceUsing[T any](ss []T, creator func(int) T, params ...int) []T {
+	var seq = func(min, max, step int) (seq []T) {
+		lenght := int(math.Round(float64(max-min) / float64(step)))
+		if lenght < 1 {
+			return
+		}
+
+		seq = make([]T, lenght)
+		for i := 0; i < lenght; min += step {
+			seq[i] = creator(min)
+			i++
+		}
+
+		return seq
+	}
+
+	if len(params) > 2 {
+		return seq(params[0], params[1], params[2])
+	} else if len(params) == 2 {
+		return seq(params[0], params[1], 1)
+	} else if len(params) == 1 {
+		return seq(0, params[0], 1)
+	}
+
+	return nil
+}
diff --git a/v2/shift.go b/v2/shift.go
new file mode 100644
index 0000000..42698b4
--- /dev/null
+++ b/v2/shift.go
@@ -0,0 +1,8 @@
+package pie
+
+import "golang.org/x/exp/constraints"
+
+// Shift will return two values: the shifted value and the rest slice.
+func Shift[T constraints.Integer | constraints.Float](ss []T) (T, []T) {
+	return FirstOr(ss, 0), DropTop(ss, 1)
+}
diff --git a/v2/shuffle.go b/v2/shuffle.go
new file mode 100644
index 0000000..221340a
--- /dev/null
+++ b/v2/shuffle.go
@@ -0,0 +1,26 @@
+package pie
+
+import (
+	"math/rand"
+)
+
+// Shuffle returns a new shuffled slice by your rand.Source. The original slice
+// is not modified.
+func Shuffle[T any](ss []T, source rand.Source) []T {
+	n := len(ss)
+
+	// Avoid the extra allocation.
+	if n < 2 {
+		return ss
+	}
+
+	shuffled := make([]T, n)
+	copy(shuffled, ss)
+
+	rnd := rand.New(source)
+	rnd.Shuffle(n, func(i, j int) {
+		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
+	})
+
+	return shuffled
+}
diff --git a/v2/sort.go b/v2/sort.go
new file mode 100644
index 0000000..7465296
--- /dev/null
+++ b/v2/sort.go
@@ -0,0 +1,26 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+	"sort"
+)
+
+// Sort works similar to sort.SliceType(). However, unlike sort.SliceType the
+// slice returned will be reallocated as to not modify the input slice.
+//
+// See Reverse() and AreSorted().
+func Sort[T constraints.Ordered](ss []T) []T {
+	// Avoid the allocation. If there is one element or less it is already
+	// sorted.
+	if len(ss) < 2 {
+		return ss
+	}
+
+	sorted := make([]T, len(ss))
+	copy(sorted, ss)
+	sort.Slice(sorted, func(i, j int) bool {
+		return sorted[i] < sorted[j]
+	})
+
+	return sorted
+}
diff --git a/v2/sort_stable_using.go b/v2/sort_stable_using.go
new file mode 100644
index 0000000..481fc84
--- /dev/null
+++ b/v2/sort_stable_using.go
@@ -0,0 +1,23 @@
+package pie
+
+import (
+	"sort"
+)
+
+// SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the
+// slice returned will be reallocated as to not modify the input slice.
+func SortStableUsing[T comparable](ss []T, less func(a, b T) bool) []T {
+	// Avoid the allocation. If there is one element or less it is already
+	// sorted.
+	if len(ss) < 2 {
+		return ss
+	}
+
+	sorted := make([]T, len(ss))
+	copy(sorted, ss)
+	sort.SliceStable(sorted, func(i, j int) bool {
+		return less(sorted[i], sorted[j])
+	})
+
+	return sorted
+}
diff --git a/v2/sort_using.go b/v2/sort_using.go
new file mode 100644
index 0000000..b6e0bf6
--- /dev/null
+++ b/v2/sort_using.go
@@ -0,0 +1,23 @@
+package pie
+
+import (
+	"sort"
+)
+
+// SortUsing works similar to sort.Slice. However, unlike sort.Slice the
+// slice returned will be reallocated as to not modify the input slice.
+func SortUsing[T any](ss []T, less func(a, b T) bool) []T {
+	// Avoid the allocation. If there is one element or less it is already
+	// sorted.
+	if len(ss) < 2 {
+		return ss
+	}
+
+	sorted := make([]T, len(ss))
+	copy(sorted, ss)
+	sort.Slice(sorted, func(i, j int) bool {
+		return less(sorted[i], sorted[j])
+	})
+
+	return sorted
+}
diff --git a/v2/stddev.go b/v2/stddev.go
new file mode 100644
index 0000000..258dead
--- /dev/null
+++ b/v2/stddev.go
@@ -0,0 +1,23 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+	"math"
+)
+
+// Stddev is the standard deviation
+func Stddev[T constraints.Integer | constraints.Float](ss []T) float64 {
+	if len(ss) == 0 {
+		return 0.0
+	}
+
+	avg := Average(ss)
+
+	var sd float64
+	for i := range ss {
+		sd += math.Pow(float64(ss[i])-avg, 2)
+	}
+	sd = math.Sqrt(sd / float64(len(ss)))
+
+	return sd
+}
diff --git a/v2/string.go b/v2/string.go
new file mode 100644
index 0000000..3283557
--- /dev/null
+++ b/v2/string.go
@@ -0,0 +1,18 @@
+package pie
+
+import (
+	"fmt"
+	"golang.org/x/exp/constraints"
+)
+
+// String transforms a value into a string. Nil values will be treated as empty
+// strings.
+//
+// If the element type implements fmt.Stringer it will be used. Otherwise it
+// will fallback to the result of:
+//
+//   fmt.Sprintf("%v")
+//
+func String[T constraints.Ordered](s T) string {
+	return fmt.Sprintf("%v", s)
+}
diff --git a/v2/strings.go b/v2/strings.go
new file mode 100644
index 0000000..7afb2d4
--- /dev/null
+++ b/v2/strings.go
@@ -0,0 +1,16 @@
+package pie
+
+import (
+	"golang.org/x/exp/constraints"
+)
+
+// 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 Strings[T constraints.Ordered](ss []T) []string {
+	return Map(ss, String[T])
+}
diff --git a/v2/strings_using.go b/v2/strings_using.go
new file mode 100644
index 0000000..5f83cd9
--- /dev/null
+++ b/v2/strings_using.go
@@ -0,0 +1,6 @@
+package pie
+
+// StringsUsing transforms each element to a string.
+func StringsUsing[T any](ss []T, transform func(T) string) []string {
+	return Map(ss, transform)
+}
diff --git a/v2/sub_slice.go b/v2/sub_slice.go
new file mode 100644
index 0000000..70f78f4
--- /dev/null
+++ b/v2/sub_slice.go
@@ -0,0 +1,32 @@
+package pie
+
+// SubSlice will return the subSlice from start to end(excluded)
+//
+// Condition 1: If start < 0 or end < 0, nil is returned.
+// Condition 2: If start >= end, nil is returned.
+// Condition 3: Return all elements that exist in the range provided,
+// if start or end is out of bounds, zero items will be placed.
+func SubSlice[T any](ss []T, start int, end int) (subSlice []T) {
+	if start < 0 || end < 0 {
+		return
+	}
+
+	if start >= end {
+		return
+	}
+
+	length := len(ss)
+	if start < length {
+		if end <= length {
+			subSlice = ss[start:end]
+		} else {
+			zeroArray := make([]T, end-length)
+			subSlice = append(ss[start:length], zeroArray[:]...)
+		}
+	} else {
+		zeroArray := make([]T, end-start)
+		subSlice = zeroArray[:]
+	}
+
+	return
+}
diff --git a/v2/sum.go b/v2/sum.go
new file mode 100644
index 0000000..579de24
--- /dev/null
+++ b/v2/sum.go
@@ -0,0 +1,12 @@
+package pie
+
+import "golang.org/x/exp/constraints"
+
+// Sum is the sum of all of the elements.
+func Sum[T constraints.Integer | constraints.Float](ss []T) (sum T) {
+	for _, s := range ss {
+		sum += s
+	}
+
+	return
+}
diff --git a/v2/top.go b/v2/top.go
new file mode 100644
index 0000000..91049f7
--- /dev/null
+++ b/v2/top.go
@@ -0,0 +1,13 @@
+package pie
+
+// 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.
+func Top[T any](ss []T, n int) (top []T) {
+	for i := 0; i < len(ss) && n > 0; i++ {
+		top = append(top, ss[i])
+		n--
+	}
+
+	return
+}
diff --git a/v2/unique.go b/v2/unique.go
new file mode 100644
index 0000000..c301ad4
--- /dev/null
+++ b/v2/unique.go
@@ -0,0 +1,27 @@
+package pie
+
+// Unique returns a new slice with all of the unique values.
+//
+// The items will be returned in a randomized order, even with the same input.
+//
+// The number of items returned may be the same as the input or less. It will
+// never return zero items unless then input slice has zero items.
+//
+// A slice with zero elements is considered to be unique.
+//
+// See AreUnique().
+func Unique[T comparable](ss []T) []T {
+	// Avoid the allocation. If there is one element or less it is already
+	// unique.
+	if len(ss) < 2 {
+		return ss
+	}
+
+	values := map[T]struct{}{}
+
+	for _, value := range ss {
+		values[value] = struct{}{}
+	}
+
+	return Keys(values)
+}
diff --git a/v2/unshift.go b/v2/unshift.go
new file mode 100644
index 0000000..d84f7c6
--- /dev/null
+++ b/v2/unshift.go
@@ -0,0 +1,10 @@
+package pie
+
+// Unshift adds one or more elements to the beginning of the slice
+// and returns the new slice.
+func Unshift[T any](ss []T, elements ...T) (unshift []T) {
+	unshift = append([]T{}, elements...)
+	unshift = append(unshift, ss...)
+
+	return
+}
diff --git a/v2/values.go b/v2/values.go
new file mode 100644
index 0000000..ba001a8
--- /dev/null
+++ b/v2/values.go
@@ -0,0 +1,21 @@
+package pie
+
+// Values returns the values in the map.
+//
+// Due to Go's randomization of iterating maps the order is not deterministic.
+func Values[K comparable, V any](m map[K]V) []V {
+	// Avoid allocation
+	l := len(m)
+	if l == 0 {
+		return nil
+	}
+
+	i := 0
+	keys := make([]V, len(m))
+	for _, value := range m {
+		keys[i] = value
+		i++
+	}
+
+	return keys
+}


Test patch:
diff --git a/v2/abs_test.go b/v2/abs_test.go
new file mode 100644
index 0000000..c072180
--- /dev/null
+++ b/v2/abs_test.go
@@ -0,0 +1,13 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestAbs(t *testing.T) {
+	assert.Equal(t, 584.2727, pie.Abs(-584.2727))
+	assert.Equal(t, 5, pie.Abs(-5))
+	assert.Equal(t, 584.2727, pie.Abs(584.2727))
+}
diff --git a/v2/all_test.go b/v2/all_test.go
new file mode 100644
index 0000000..ffb5c84
--- /dev/null
+++ b/v2/all_test.go
@@ -0,0 +1,36 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestAll(t *testing.T) {
+	assert.True(t,
+		pie.All([]float64{}, func(value float64) bool {
+			return false
+		}),
+	)
+
+	// None
+	assert.False(t,
+		pie.All([]float64{12.3, 4.56}, func(value float64) bool {
+			return value == 1
+		}),
+	)
+
+	// Some
+	assert.False(t,
+		pie.All([]float64{12.3, 4.56}, func(value float64) bool {
+			return value == 12.3
+		}),
+	)
+
+	// All
+	assert.True(t,
+		pie.All([]float64{12.3, 4.56}, func(value float64) bool {
+			return value > 0
+		}),
+	)
+}
diff --git a/v2/any_test.go b/v2/any_test.go
new file mode 100644
index 0000000..f777d6f
--- /dev/null
+++ b/v2/any_test.go
@@ -0,0 +1,36 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestAny(t *testing.T) {
+	assert.False(t,
+		pie.Any([]float64{}, func(value float64) bool {
+			return true
+		}),
+	)
+
+	// None
+	assert.False(t,
+		pie.Any([]float64{12.3, 4.56}, func(value float64) bool {
+			return value == 1
+		}),
+	)
+
+	// Some
+	assert.True(t,
+		pie.Any([]float64{12.3, 4.56}, func(value float64) bool {
+			return value == 12.3
+		}),
+	)
+
+	// All
+	assert.True(t,
+		pie.Any([]float64{12.3, 4.56}, func(value float64) bool {
+			return value > 0
+		}),
+	)
+}
diff --git a/v2/are_sorted_test.go b/v2/are_sorted_test.go
new file mode 100644
index 0000000..8acccf1
--- /dev/null
+++ b/v2/are_sorted_test.go
@@ -0,0 +1,59 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var sortTests = []struct {
+	ss        []float64
+	sorted    []float64
+	reversed  []float64
+	areSorted bool
+}{
+	{
+		nil,
+		nil,
+		nil,
+		true,
+	},
+	{
+		[]float64{},
+		[]float64{},
+		[]float64{},
+		true,
+	},
+	{
+		[]float64{789},
+		[]float64{789},
+		[]float64{789},
+		true,
+	},
+	{
+		[]float64{12.789, -13.2, 789},
+		[]float64{-13.2, 12.789, 789},
+		[]float64{789, -13.2, 12.789},
+		false,
+	},
+	{
+		[]float64{12.789, -13.2, 1.234e6, 789},
+		[]float64{-13.2, 12.789, 789, 1.234e6},
+		[]float64{789, 1.234e6, -13.2, 12.789},
+		false,
+	},
+	{
+		[]float64{-13.2, 12.789},
+		[]float64{-13.2, 12.789},
+		[]float64{12.789, -13.2},
+		true,
+	},
+}
+
+func TestAreSorted(t *testing.T) {
+	for _, test := range sortTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.areSorted, pie.AreSorted(test.ss))
+		})
+	}
+}
diff --git a/v2/are_unique_test.go b/v2/are_unique_test.go
new file mode 100644
index 0000000..2353272
--- /dev/null
+++ b/v2/are_unique_test.go
@@ -0,0 +1,47 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var uniqueTests = []struct {
+	ss        []float64
+	unique    []float64
+	areUnique bool
+}{
+	{
+		nil,
+		nil,
+		true,
+	},
+	{
+		[]float64{},
+		[]float64{},
+		true,
+	},
+	{
+		[]float64{789},
+		[]float64{789},
+		true,
+	},
+	{
+		[]float64{12.789, -13.2, 12.789},
+		[]float64{-13.2, 12.789},
+		false,
+	},
+	{
+		[]float64{12.789, -13.2, 1.234e6, 789},
+		[]float64{-13.2, 12.789, 789, 1.234e6},
+		true,
+	},
+}
+
+func TestAreUnique(t *testing.T) {
+	for _, test := range uniqueTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.areUnique, pie.AreUnique(test.ss))
+		})
+	}
+}
diff --git a/v2/average_test.go b/v2/average_test.go
new file mode 100644
index 0000000..04c2fc7
--- /dev/null
+++ b/v2/average_test.go
@@ -0,0 +1,64 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var statsTests = []struct {
+	ss                     []float64
+	min, max, sum, product float64
+	len                    int
+	mode                   []float64
+	average                float64
+}{
+	{
+		nil,
+		0,
+		0,
+		0,
+		0,
+		0,
+		nil,
+		0,
+	},
+	{
+		[]float64{},
+		0,
+		0,
+		0,
+		0,
+		0,
+		[]float64{},
+		0,
+	},
+	{
+		[]float64{1.5},
+		1.5,
+		1.5,
+		1.5,
+		1.5,
+		1,
+		[]float64{1.5},
+		1.5,
+	},
+	{
+		[]float64{2.2, 3.1, 5.1, 1.9},
+		1.9,
+		5.1,
+		12.3,
+		66.0858,
+		4,
+		[]float64{2.2, 3.1, 5.1, 1.9},
+		3.075,
+	},
+}
+
+func TestAverage(t *testing.T) {
+	for _, test := range statsTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.average, pie.Average(test.ss))
+		})
+	}
+}
diff --git a/v2/bottom_test.go b/v2/bottom_test.go
new file mode 100644
index 0000000..a2bc0ad
--- /dev/null
+++ b/v2/bottom_test.go
@@ -0,0 +1,59 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var topAndBottomTests = []struct {
+	ss     []float64
+	n      int
+	top    []float64
+	bottom []float64
+}{
+	{
+		nil,
+		1,
+		nil,
+		nil,
+	},
+	{
+		[]float64{},
+		1,
+		nil,
+		nil,
+	},
+	{
+		[]float64{1.23, 2.34},
+		1,
+		[]float64{1.23},
+		[]float64{2.34},
+	},
+	{
+		[]float64{1.23, 2.34},
+		3,
+		[]float64{1.23, 2.34},
+		[]float64{2.34, 1.23},
+	},
+	{
+		[]float64{1.23, 2.34},
+		0,
+		nil,
+		nil,
+	},
+	{
+		[]float64{1.23, 2.34},
+		-1,
+		nil,
+		nil,
+	},
+}
+
+func TestBottom(t *testing.T) {
+	for _, test := range topAndBottomTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.bottom, pie.Bottom(test.ss, test.n))
+		})
+	}
+}
diff --git a/v2/contains_test.go b/v2/contains_test.go
new file mode 100644
index 0000000..952923d
--- /dev/null
+++ b/v2/contains_test.go
@@ -0,0 +1,30 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var containsTests = []struct {
+	ss       []float64
+	contains float64
+	expected bool
+}{
+	{nil, 1, false},
+	{[]float64{1, 2, 3}, 1, true},
+	{[]float64{1, 2, 3}, 2, true},
+	{[]float64{1, 2, 3}, 3, true},
+	{[]float64{1, 2, 3}, 4, false},
+	{[]float64{1, 2, 3}, 5, false},
+	{[]float64{1, 2, 3}, 6, false},
+	{[]float64{1, 5, 3}, 5, true},
+}
+
+func TestContains(t *testing.T) {
+	for _, test := range containsTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, pie.Contains(test.ss, test.contains))
+		})
+	}
+}
diff --git a/v2/diff_test.go b/v2/diff_test.go
new file mode 100644
index 0000000..e5c66b8
--- /dev/null
+++ b/v2/diff_test.go
@@ -0,0 +1,67 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var diffTests = map[string]struct {
+	ss1     []float64
+	ss2     []float64
+	added   []float64
+	removed []float64
+}{
+	"BothEmpty": {
+		nil,
+		nil,
+		nil,
+		nil,
+	},
+	"OnlyRemovedUnique": {
+		[]float64{4334.5435, 879.123},
+		nil,
+		nil,
+		[]float64{4334.5435, 879.123},
+	},
+	"OnlyRemovedDuplicates": {
+		[]float64{4334.5435, 92.384, 4334.5435},
+		nil,
+		nil,
+		[]float64{4334.5435, 92.384, 4334.5435},
+	},
+	"OnlyAddedUnique": {
+		nil,
+		[]float64{879.123, 92.384},
+		[]float64{879.123, 92.384},
+		nil,
+	},
+	"OnlyAddedDuplicates": {
+		nil,
+		[]float64{879.123, 92.384, 92.384, 879.123},
+		[]float64{879.123, 92.384, 92.384, 879.123},
+		nil,
+	},
+	"AddedAndRemovedUnique": {
+		[]float64{4334.5435, 879.123, 92.384, 823.324},
+		[]float64{92.384, 823.324, 453, 3.345},
+		[]float64{453, 3.345},
+		[]float64{4334.5435, 879.123},
+	},
+	"AddedAndRemovedDuplicates": {
+		[]float64{4334.5435, 879.123, 92.384, 92.384, 823.324},
+		[]float64{92.384, 823.324, 453, 823.324, 3.345},
+		[]float64{453, 823.324, 3.345},
+		[]float64{4334.5435, 879.123, 92.384},
+	},
+}
+
+func TestDiff(t *testing.T) {
+	for testName, test := range diffTests {
+		t.Run(testName, func(t *testing.T) {
+			added, removed := pie.Diff(test.ss1, test.ss2)
+			assert.Equal(t, test.added, added)
+			assert.Equal(t, test.removed, removed)
+		})
+	}
+}
diff --git a/v2/drop_top_test.go b/v2/drop_top_test.go
new file mode 100644
index 0000000..84f1b56
--- /dev/null
+++ b/v2/drop_top_test.go
@@ -0,0 +1,58 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var dropTopTests = []struct {
+	ss      []float64
+	n       int
+	dropTop []float64
+}{
+	{
+		nil,
+		1,
+		nil,
+	},
+	{
+		[]float64{},
+		1,
+		nil,
+	},
+	{
+		[]float64{1.23, 2.34},
+		-1,
+		nil,
+	},
+	{
+		[]float64{1.23, 2.34},
+		0,
+		[]float64{1.23, 2.34},
+	},
+
+	{
+		[]float64{1.23, 2.34},
+		1,
+		[]float64{2.34},
+	},
+	{
+		[]float64{1.23, 2.34},
+		2,
+		nil,
+	},
+	{
+		[]float64{1.23, 2.34},
+		3,
+		nil,
+	},
+}
+
+func TestDropTop(t *testing.T) {
+	for _, test := range dropTopTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.dropTop, pie.DropTop(test.ss, test.n))
+		})
+	}
+}
diff --git a/v2/drop_while_test.go b/v2/drop_while_test.go
new file mode 100644
index 0000000..f45e6b5
--- /dev/null
+++ b/v2/drop_while_test.go
@@ -0,0 +1,42 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var dropWhileTests = []struct {
+	ss        []float64
+	f         func(s float64) bool
+	dropWhile []float64
+}{
+	{
+		ss:        nil,
+		f:         func(s float64) bool { return s == 0.1 },
+		dropWhile: []float64{},
+	},
+	{
+		ss:        []float64{2.1, 2.1, 2.1, 7.2, 8.1},
+		f:         func(s float64) bool { return s == 2.1 },
+		dropWhile: []float64{7.2, 8.1},
+	},
+	{
+		ss:        []float64{2.1, 4.1, 5.1, 7.2, 8.1},
+		f:         func(s float64) bool { return s == 0.2 },
+		dropWhile: []float64{2.1, 4.1, 5.1, 7.2, 8.1},
+	},
+	{
+		ss:        []float64{2.1, 2.1, 2.1, 2.1, 2.1},
+		f:         func(s float64) bool { return s == 2.1 },
+		dropWhile: []float64{},
+	},
+}
+
+func TestDropWhile(t *testing.T) {
+	for _, test := range dropWhileTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.dropWhile, pie.DropWhile(test.ss, test.f))
+		})
+	}
+}
diff --git a/v2/each_test.go b/v2/each_test.go
new file mode 100644
index 0000000..7ad9c83
--- /dev/null
+++ b/v2/each_test.go
@@ -0,0 +1,23 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestEach(t *testing.T) {
+	var values []float64
+
+	values = []float64{}
+	pie.Each([]float64{}, func(value float64) {
+		values = append(values, value)
+	})
+	assert.Equal(t, []float64{}, values)
+
+	values = []float64{}
+	pie.Each([]float64{435.34, 8923.1}, func(value float64) {
+		values = append(values, value)
+	})
+	assert.Equal(t, []float64{435.34, 8923.1}, values)
+}
diff --git a/v2/equals_test.go b/v2/equals_test.go
new file mode 100644
index 0000000..37477ef
--- /dev/null
+++ b/v2/equals_test.go
@@ -0,0 +1,30 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var equalsTests = []struct {
+	ss       []float64
+	rhs      []float64
+	expected bool
+}{
+	{nil, nil, true},
+	{[]float64{}, []float64{}, true},
+	{nil, []float64{}, true},
+	{[]float64{1.0, 2.0}, []float64{1.0, 2.0}, true},
+	{[]float64{1.0, 2.0}, []float64{1.0, 5.0}, false},
+	{[]float64{1.0, 2.0}, []float64{1.0}, false},
+	{[]float64{1.0}, []float64{2.0}, false},
+	{[]float64{1.0}, nil, false},
+}
+
+func TestEquals(t *testing.T) {
+	for _, test := range equalsTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, pie.Equals(test.ss, test.rhs))
+		})
+	}
+}
diff --git a/v2/filter_not_test.go b/v2/filter_not_test.go
new file mode 100644
index 0000000..6cb6f37
--- /dev/null
+++ b/v2/filter_not_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestFilterNot(t *testing.T) {
+	for _, test := range selectTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expectedFilterNot, pie.FilterNot(test.ss, test.condition))
+		})
+	}
+}
diff --git a/v2/filter_test.go b/v2/filter_test.go
new file mode 100644
index 0000000..608781b
--- /dev/null
+++ b/v2/filter_test.go
@@ -0,0 +1,42 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var selectTests = []struct {
+	ss                []float64
+	condition         func(float64) bool
+	expectedFilter    []float64
+	expectedFilterNot []float64
+	expectedMap       []float64
+}{
+	{
+		nil,
+		func(s float64) bool {
+			return s == 5
+		},
+		nil,
+		nil,
+		nil,
+	},
+	{
+		[]float64{1, 2, 3},
+		func(s float64) bool {
+			return s != 2
+		},
+		[]float64{1, 3},
+		[]float64{2},
+		[]float64{6.2, 7.2, 8.2},
+	},
+}
+
+func TestFilter(t *testing.T) {
+	for _, test := range selectTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expectedFilter, pie.Filter(test.ss, test.condition))
+		})
+	}
+}
diff --git a/v2/find_first_using_test.go b/v2/find_first_using_test.go
new file mode 100644
index 0000000..7488f4f
--- /dev/null
+++ b/v2/find_first_using_test.go
@@ -0,0 +1,47 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var findFirstUsingTests = []struct {
+	ss         []float64
+	expression func(value float64) bool
+	expected   int
+}{
+	{
+		nil,
+		func(value float64) bool { return value == 1.5 },
+		-1,
+	},
+	{
+		[]float64{},
+		func(value float64) bool { return value == 0.1 },
+		-1,
+	},
+	{
+		[]float64{0.0, 1.5, 3.2},
+		func(value float64) bool { return value == 9.99 },
+		-1,
+	},
+	{
+		[]float64{5.4, 6.98, 4.987, 33.04},
+		func(value float64) bool { return value == 33.04 },
+		3,
+	},
+	{
+		[]float64{9.0, 0.11, 150.44, 33.04},
+		func(value float64) bool { return value == 0.11 },
+		1,
+	},
+}
+
+func TestFindFirstUsing(t *testing.T) {
+	for _, test := range findFirstUsingTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, pie.FindFirstUsing(test.ss, test.expression))
+		})
+	}
+}
diff --git a/v2/first_or_test.go b/v2/first_or_test.go
new file mode 100644
index 0000000..5741ec7
--- /dev/null
+++ b/v2/first_or_test.go
@@ -0,0 +1,42 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var firstAndLastTests = []struct {
+	ss      []float64
+	firstOr float64
+	lastOr  float64
+}{
+	{
+		nil,
+		102,
+		202,
+	},
+	{
+		[]float64{100},
+		100,
+		100,
+	},
+	{
+		[]float64{1, 2},
+		1,
+		2,
+	},
+	{
+		[]float64{1, 2, 3},
+		1,
+		3,
+	},
+}
+
+func TestFirstOr(t *testing.T) {
+	for _, test := range firstAndLastTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.firstOr, pie.FirstOr(test.ss, 102))
+		})
+	}
+}
diff --git a/v2/float64_test.go b/v2/float64_test.go
new file mode 100644
index 0000000..968a9b0
--- /dev/null
+++ b/v2/float64_test.go
@@ -0,0 +1,13 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestFloat64(t *testing.T) {
+	assert.Equal(t, 123.0, pie.Float64(123))
+	assert.Equal(t, 1.89, pie.Float64(1.89))
+	assert.Equal(t, 1.89, pie.Float64("1.89"))
+}
diff --git a/v2/float64s_test.go b/v2/float64s_test.go
new file mode 100644
index 0000000..affda44
--- /dev/null
+++ b/v2/float64s_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestFloat64s(t *testing.T) {
+	assert.Equal(t, []float64(nil), pie.Float64s([]float64(nil)))
+
+	assert.Equal(t,
+		[]float64{92.384, 823.324, 453},
+		pie.Float64s([]float64{92.384, 823.324, 453}))
+}
diff --git a/v2/group_test.go b/v2/group_test.go
new file mode 100644
index 0000000..dc78833
--- /dev/null
+++ b/v2/group_test.go
@@ -0,0 +1,21 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestGroup(t *testing.T) {
+	assert.Equal(t, map[float64]int{}, pie.Group([]float64{}))
+
+	assert.Equal(t, map[float64]int{
+		1: 1,
+	}, pie.Group([]float64{1}))
+
+	assert.Equal(t, map[float64]int{
+		1: 1,
+		2: 2,
+		3: 3,
+	}, pie.Group([]float64{1, 2, 2, 3, 3, 3}))
+}
diff --git a/v2/insert_test.go b/v2/insert_test.go
new file mode 100644
index 0000000..5a9c80b
--- /dev/null
+++ b/v2/insert_test.go
@@ -0,0 +1,14 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestInsert(t *testing.T) {
+	assert.Equal(t, []float64(nil), pie.Insert([]float64(nil), 0))
+	assert.Equal(t, []float64{2.0, 1.0}, pie.Insert([]float64{1.0}, 0, 2.0))
+	assert.Equal(t, []float64{1.0, 2.0}, pie.Insert([]float64{1.0}, 1, 2.0))
+	assert.Equal(t, []float64{1.0, 2.0, 3.3}, pie.Insert([]float64{1.0, 3.3}, 1, 2.0))
+}
diff --git a/v2/int_test.go b/v2/int_test.go
new file mode 100644
index 0000000..ea9199e
--- /dev/null
+++ b/v2/int_test.go
@@ -0,0 +1,13 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestInt(t *testing.T) {
+	assert.Equal(t, 123, pie.Int(123))
+	assert.Equal(t, 1, pie.Int(1.89))
+	assert.Equal(t, 1, pie.Int("1.89"))
+}
diff --git a/v2/intersect_test.go b/v2/intersect_test.go
new file mode 100644
index 0000000..7a17d2a
--- /dev/null
+++ b/v2/intersect_test.go
@@ -0,0 +1,52 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var intersectTests = []struct {
+	ss       []float64
+	params   [][]float64
+	expected []float64
+}{
+	{
+		nil,
+		nil,
+		nil,
+	},
+	{
+		[]float64{1.2, 3.2},
+		nil,
+		nil,
+	},
+	{
+		nil,
+		[][]float64{{1.2, 3.2, 5.5}, {5.5, 1.2}},
+		nil,
+	},
+	{
+		[]float64{1.2, 3.2},
+		[][]float64{{1.2}, {3.2}},
+		nil,
+	},
+	{
+		[]float64{1.2, 3.2},
+		[][]float64{{1.2}},
+		[]float64{1.2},
+	},
+	{
+		[]float64{1.2, 3.2},
+		[][]float64{{1.2, 3.2, 5.5}, {5.5, 1.2}},
+		[]float64{1.2},
+	},
+}
+
+func TestIntersect(t *testing.T) {
+	for _, test := range intersectTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, pie.Intersect(test.ss, test.params...))
+		})
+	}
+}
diff --git a/v2/ints_test.go b/v2/ints_test.go
new file mode 100644
index 0000000..533bee6
--- /dev/null
+++ b/v2/ints_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestInts(t *testing.T) {
+	assert.Equal(t, []int(nil), pie.Ints([]int(nil)))
+
+	assert.Equal(t,
+		[]int{92, 823, 453},
+		pie.Ints([]float64{92.384, 823.324, 453}))
+}
diff --git a/v2/join_test.go b/v2/join_test.go
new file mode 100644
index 0000000..98c3cad
--- /dev/null
+++ b/v2/join_test.go
@@ -0,0 +1,14 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestJoin(t *testing.T) {
+	assert.Equal(t, "", pie.Join([]float64(nil), "a"))
+	assert.Equal(t, "", pie.Join([]float64{}, "a"))
+	var f1, f2 float64 = 0.1, 20000000000000000
+	assert.Equal(t, "0.1;2e+16", pie.Join([]float64{f1, f2}, ";"))
+}
diff --git a/v2/json_bytes_indent_test.go b/v2/json_bytes_indent_test.go
new file mode 100644
index 0000000..7335d6a
--- /dev/null
+++ b/v2/json_bytes_indent_test.go
@@ -0,0 +1,44 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var jsonIndentTests = []struct {
+	ss         []float64
+	jsonString string
+}{
+	{
+		nil,
+		`[]`, // Instead of null.
+	},
+	{
+		[]float64{},
+		`[]`,
+	},
+	{
+		[]float64{12.3},
+		`[
+  12.3
+]`,
+	},
+	{
+		[]float64{23, -2.5, 3424, 12.3},
+		`[
+  23,
+  -2.5,
+  3424,
+  12.3
+]`,
+	},
+}
+
+func TestJSONBytesIndent(t *testing.T) {
+	for _, test := range jsonIndentTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, []byte(test.jsonString), pie.JSONBytesIndent(test.ss, "", "  "))
+		})
+	}
+}
diff --git a/v2/json_bytes_test.go b/v2/json_bytes_test.go
new file mode 100644
index 0000000..66bafe7
--- /dev/null
+++ b/v2/json_bytes_test.go
@@ -0,0 +1,37 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var jsonTests = []struct {
+	ss         []float64
+	jsonString string
+}{
+	{
+		nil,
+		`[]`, // Instead of null.
+	},
+	{
+		[]float64{},
+		`[]`,
+	},
+	{
+		[]float64{12.3},
+		`[12.3]`,
+	},
+	{
+		[]float64{23, -2.5, 3424, 12.3},
+		`[23,-2.5,3424,12.3]`,
+	},
+}
+
+func TestJSONBytes(t *testing.T) {
+	for _, test := range jsonTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, []byte(test.jsonString), pie.JSONBytes(test.ss))
+		})
+	}
+}
diff --git a/v2/json_string_indent_test.go b/v2/json_string_indent_test.go
new file mode 100644
index 0000000..611d6c5
--- /dev/null
+++ b/v2/json_string_indent_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestJSONStringIndent(t *testing.T) {
+	for _, test := range jsonIndentTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.jsonString, pie.JSONStringIndent(test.ss, "", "  "))
+		})
+	}
+}
diff --git a/v2/json_string_test.go b/v2/json_string_test.go
new file mode 100644
index 0000000..04e3fda
--- /dev/null
+++ b/v2/json_string_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestJSONString(t *testing.T) {
+	for _, test := range jsonTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.jsonString, pie.JSONString(test.ss))
+		})
+	}
+}
diff --git a/v2/keys_test.go b/v2/keys_test.go
new file mode 100644
index 0000000..4f90e18
--- /dev/null
+++ b/v2/keys_test.go
@@ -0,0 +1,29 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"sort"
+	"testing"
+)
+
+type currency struct {
+	NumericCode, Exponent int
+}
+
+type currencies map[string]currency
+
+var isoCurrencies = currencies{
+	"AUD": {36, -2},
+	"USD": {840, -2},
+}
+
+func TestKeys(t *testing.T) {
+	assert.Equal(t, []string(nil), pie.Keys(currencies(nil)))
+
+	assert.Equal(t, []string(nil), pie.Keys(currencies{}))
+
+	keys := pie.Keys(isoCurrencies)
+	sort.Strings(keys)
+	assert.Equal(t, []string{"AUD", "USD"}, keys)
+}
diff --git a/v2/last_or_test.go b/v2/last_or_test.go
new file mode 100644
index 0000000..86da448
--- /dev/null
+++ b/v2/last_or_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestLastOr(t *testing.T) {
+	for _, test := range firstAndLastTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.lastOr, pie.LastOr(test.ss, 202))
+		})
+	}
+}
diff --git a/v2/map_test.go b/v2/map_test.go
new file mode 100644
index 0000000..ba808b0
--- /dev/null
+++ b/v2/map_test.go
@@ -0,0 +1,17 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestMap(t *testing.T) {
+	for _, test := range selectTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expectedMap, pie.Map(test.ss, func(a float64) float64 {
+				return a + 5.2
+			}))
+		})
+	}
+}
diff --git a/v2/max_test.go b/v2/max_test.go
new file mode 100644
index 0000000..30d1119
--- /dev/null
+++ b/v2/max_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestMax(t *testing.T) {
+	for _, test := range statsTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.max, pie.Max(test.ss))
+		})
+	}
+}
diff --git a/v2/median_test.go b/v2/median_test.go
new file mode 100644
index 0000000..91bce69
--- /dev/null
+++ b/v2/median_test.go
@@ -0,0 +1,14 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestMedian(t *testing.T) {
+	assert.Equal(t, 0.0, pie.Median([]float64{}))
+	assert.Equal(t, 12.3, pie.Median([]float64{12.3}))
+	assert.Equal(t, 8.4, pie.Median([]float64{12.3, 4.5}))
+	assert.Equal(t, 4.5, pie.Median([]float64{2.1, 12.3, 4.5}))
+}
diff --git a/v2/min_test.go b/v2/min_test.go
new file mode 100644
index 0000000..920cbfa
--- /dev/null
+++ b/v2/min_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestMin(t *testing.T) {
+	for _, test := range statsTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.min, pie.Min(test.ss))
+		})
+	}
+}
diff --git a/v2/mode_test.go b/v2/mode_test.go
new file mode 100644
index 0000000..efd85c8
--- /dev/null
+++ b/v2/mode_test.go
@@ -0,0 +1,30 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestMode(t *testing.T) {
+	cmp := func(a, b []float64) bool {
+		m := make(map[float64]struct{})
+		for _, i := range a {
+			m[i] = struct{}{}
+		}
+
+		for _, i := range b {
+			if _, ok := m[i]; !ok {
+				return false
+			}
+		}
+
+		return true
+	}
+
+	for _, test := range statsTests {
+		t.Run("", func(t *testing.T) {
+			assert.True(t, cmp(test.mode, pie.Mode(test.ss)))
+		})
+	}
+}
diff --git a/v2/of_numeric_test.go b/v2/of_numeric_test.go
new file mode 100644
index 0000000..f54a9f5
--- /dev/null
+++ b/v2/of_numeric_test.go
@@ -0,0 +1,26 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestOfONumeric(t *testing.T) {
+	t.Run("chaining", func(t *testing.T) {
+		total := pie.OfNumeric([]float64{123, 456}).
+			Sum()
+
+		assert.Equal(t, 579.0, total)
+	})
+
+	t.Run("result", func(t *testing.T) {
+		names := pie.OfNumeric([]float64{1.23, 4.56}).
+			Filter(func(x float64) bool {
+				return x < 4
+			}).
+			Result
+
+		assert.Equal(t, []float64{1.23}, names)
+	})
+}
diff --git a/v2/of_ordered_test.go b/v2/of_ordered_test.go
new file mode 100644
index 0000000..71d1b32
--- /dev/null
+++ b/v2/of_ordered_test.go
@@ -0,0 +1,26 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestOfOrdered(t *testing.T) {
+	t.Run("chaining", func(t *testing.T) {
+		name := pie.OfOrdered([]string{"Bob", "Sally", "John", "Jane"}).
+			Join("+")
+
+		assert.Equal(t, "Bob+Sally+John+Jane", name)
+	})
+
+	t.Run("result", func(t *testing.T) {
+		names := pie.OfOrdered([]string{"Bob", "Sally", "John", "Jane"}).
+			Filter(func(s string) bool {
+				return len(s) < 4
+			}).
+			Result
+
+		assert.Equal(t, []string{"Bob"}, names)
+	})
+}
diff --git a/v2/of_test.go b/v2/of_test.go
new file mode 100644
index 0000000..e542ac5
--- /dev/null
+++ b/v2/of_test.go
@@ -0,0 +1,31 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"strings"
+	"testing"
+)
+
+func TestOf(t *testing.T) {
+	t.Run("chaining", func(t *testing.T) {
+		name := pie.Of([]string{"Bob", "Sally", "John", "Jane"}).
+			FilterNot(func(name string) bool {
+				return strings.HasPrefix(name, "J")
+			}).
+			Map(strings.ToUpper).
+			LastOr("")
+
+		assert.Equal(t, "SALLY", name)
+	})
+
+	t.Run("result", func(t *testing.T) {
+		names := pie.Of([]string{"Bob", "Sally", "John", "Jane"}).
+			FilterNot(func(name string) bool {
+				return strings.HasPrefix(name, "J")
+			}).
+			Result
+
+		assert.Equal(t, []string{"Bob", "Sally"}, names)
+	})
+}
diff --git a/v2/pop_test.go b/v2/pop_test.go
new file mode 100644
index 0000000..3f751b5
--- /dev/null
+++ b/v2/pop_test.go
@@ -0,0 +1,17 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestPop(t *testing.T) {
+	numbers := []float64{42.0, 4.2}
+
+	assert.Equal(t, 42.0, *pie.Pop(&numbers))
+	assert.Equal(t, []float64{4.2}, numbers)
+
+	assert.Equal(t, 4.2, *pie.Pop(&numbers))
+	assert.Equal(t, []float64{}, numbers)
+}
diff --git a/v2/product_test.go b/v2/product_test.go
new file mode 100644
index 0000000..f920cb2
--- /dev/null
+++ b/v2/product_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestProduct(t *testing.T) {
+	for _, test := range statsTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.product, pie.Product(test.ss))
+		})
+	}
+}
diff --git a/v2/random_test.go b/v2/random_test.go
new file mode 100644
index 0000000..befe540
--- /dev/null
+++ b/v2/random_test.go
@@ -0,0 +1,53 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"math/rand"
+	"testing"
+)
+
+var randomTests = []struct {
+	ss       []float64
+	expected float64
+	source   rand.Source
+}{
+	{
+		nil,
+		0.0,
+		nil,
+	},
+	{
+		nil,
+		0.0,
+		rand.NewSource(0),
+	},
+	{
+		[]float64{},
+		0.0,
+		rand.NewSource(0),
+	},
+	{
+		[]float64{12.3, 2.34, 4.56},
+		12.3,
+		rand.NewSource(0),
+	},
+	{
+		[]float64{12.3, 2.34, 4.56},
+		4.56,
+		rand.NewSource(1),
+	},
+	{
+		[]float64{12.3},
+		12.3,
+		rand.NewSource(0),
+	},
+}
+
+func TestRandom(t *testing.T) {
+	for _, test := range randomTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, pie.Random(test.ss, test.source))
+		})
+	}
+}
diff --git a/v2/reduce_test.go b/v2/reduce_test.go
new file mode 100644
index 0000000..37fddae
--- /dev/null
+++ b/v2/reduce_test.go
@@ -0,0 +1,42 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var reduceTests = []struct {
+	ss       []float64
+	expected float64
+	reducer  func(a, b float64) float64
+}{
+	{
+		[]float64{1, 2, 3},
+		6,
+		func(a, b float64) float64 { return a + b },
+	},
+	{
+		[]float64{1, 2, 3},
+		-4,
+		func(a, b float64) float64 { return a - b },
+	},
+	{
+		[]float64{},
+		0,
+		func(a, b float64) float64 { return a - b },
+	},
+	{
+		[]float64{1},
+		1,
+		func(a, b float64) float64 { return a - b },
+	},
+}
+
+func TestReduce(t *testing.T) {
+	for _, test := range reduceTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, pie.Reduce(test.ss, test.reducer))
+		})
+	}
+}
diff --git a/v2/reverse_test.go b/v2/reverse_test.go
new file mode 100644
index 0000000..7a29d32
--- /dev/null
+++ b/v2/reverse_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestReverse(t *testing.T) {
+	for _, test := range sortTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.reversed, pie.Reverse(test.ss))
+		})
+	}
+}
diff --git a/v2/send_test.go b/v2/send_test.go
new file mode 100644
index 0000000..161886a
--- /dev/null
+++ b/v2/send_test.go
@@ -0,0 +1,100 @@
+package pie_test
+
+import (
+	"context"
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+	"time"
+)
+
+var sendTests = []struct {
+	ss            []float64
+	recieveDelay  time.Duration
+	canceledDelay time.Duration
+	expected      []float64
+}{
+	{
+		nil,
+		0,
+		0,
+		nil,
+	},
+	{
+		[]float64{1.2, 3.2},
+		0,
+		0,
+		[]float64{1.2, 3.2},
+	},
+	{
+		[]float64{1.2, 3.2},
+		time.Millisecond * 30,
+		time.Millisecond * 10,
+		[]float64{1.2},
+	},
+	{
+		[]float64{1.2, 3.2},
+		time.Millisecond * 3,
+		time.Millisecond * 10,
+		[]float64{1.2, 3.2},
+	},
+}
+
+func TestSend(t *testing.T) {
+	for _, test := range sendTests {
+		t.Run("", func(t *testing.T) {
+			ch := make(chan float64)
+
+			actual := getFloat64sFromChan(ch, test.recieveDelay)
+			ctx := createContextByDelay(test.canceledDelay)
+
+			actualSent := pie.Send(ctx, test.ss, ch)
+			close(ch)
+
+			assert.Equal(t, test.expected, actualSent)
+			assert.Equal(t, test.expected, actual())
+		})
+	}
+}
+
+func getFloat64sFromChan(ch chan float64, t time.Duration) func() []float64 {
+	done := make(chan struct{})
+	var c []float64
+	if t > 0 {
+		go func() {
+			ticker := time.NewTicker(t)
+			defer ticker.Stop()
+			for range ticker.C {
+				val, ok := <-ch
+				if !ok {
+					break
+				} else {
+					c = append(c, val)
+				}
+			}
+			done <- struct{}{}
+
+		}()
+	} else {
+		go func() {
+			for val := range ch {
+				c = append(c, val)
+			}
+			done <- struct{}{}
+		}()
+	}
+
+	return func() []float64 {
+		<-done
+		return c
+	}
+}
+
+func createContextByDelay(t time.Duration) context.Context {
+	ctx := context.Background()
+	if t > 0 {
+		ctx, _ = context.WithTimeout(ctx, t)
+	}
+
+	return ctx
+}
diff --git a/v2/sequence_test.go b/v2/sequence_test.go
new file mode 100644
index 0000000..177b1ef
--- /dev/null
+++ b/v2/sequence_test.go
@@ -0,0 +1,110 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var sequenceAndSequenceUsingTests = []struct {
+	ss       []float64
+	params   []int
+	expected []float64
+}{
+	// n
+	{
+		nil,
+		nil,
+		nil,
+	},
+	{
+		nil,
+		[]int{-1},
+		nil,
+	},
+	{
+		nil,
+		[]int{0},
+		nil,
+	},
+	{
+		nil,
+		[]int{3},
+		[]float64{0, 1, 2},
+	},
+	{
+		[]float64{},
+		[]int{3},
+		[]float64{0, 1, 2},
+	},
+	// range
+	{
+		nil,
+		[]int{2, 2},
+		nil,
+	},
+	{
+		[]float64{},
+		[]int{3, 2},
+		nil,
+	},
+	{
+		nil,
+		[]int{0, 3},
+		[]float64{0, 1, 2},
+	},
+	{
+		[]float64{},
+		[]int{3, 6},
+		[]float64{3, 4, 5},
+	},
+	{
+		[]float64{},
+		[]int{-5, 0},
+		[]float64{-5, -4, -3, -2, -1},
+	},
+	{
+		[]float64{},
+		[]int{-5, -10},
+		nil,
+	},
+	// range with step
+	{
+		nil,
+		[]int{3, 3, 1},
+		nil,
+	},
+	{
+		[]float64{},
+		[]int{3, 6, 2},
+		[]float64{3, 5},
+	},
+	{
+		[]float64{},
+		[]int{3, 7, 2},
+		[]float64{3, 5},
+	},
+	{
+		[]float64{},
+		[]int{-10, -6, 1},
+		[]float64{-10, -9, -8, -7},
+	},
+	{
+		[]float64{},
+		[]int{-6, -10, -1},
+		[]float64{-6, -7, -8, -9},
+	},
+	{
+		[]float64{},
+		[]int{-6, -10, 1},
+		nil,
+	},
+}
+
+func TestSequence(t *testing.T) {
+	for _, test := range sequenceAndSequenceUsingTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, pie.Sequence(test.ss, test.params...))
+		})
+	}
+}
diff --git a/v2/sequence_using_test.go b/v2/sequence_using_test.go
new file mode 100644
index 0000000..072bbc9
--- /dev/null
+++ b/v2/sequence_using_test.go
@@ -0,0 +1,16 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestSequenceUsing(t *testing.T) {
+	for _, test := range sequenceAndSequenceUsingTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, pie.SequenceUsing(test.ss,
+				func(i int) float64 { return float64(i) }, test.params...))
+		})
+	}
+}
diff --git a/v2/shift_test.go b/v2/shift_test.go
new file mode 100644
index 0000000..d27768a
--- /dev/null
+++ b/v2/shift_test.go
@@ -0,0 +1,90 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var shiftAndUnshiftTests = []struct {
+	ss      []float64
+	shifted float64
+	shift   []float64
+	params  []float64
+	unshift []float64
+}{
+	{
+		nil,
+		0,
+		nil,
+		nil,
+		[]float64{},
+	},
+	{
+		nil,
+		0,
+		nil,
+		[]float64{},
+		[]float64{},
+	},
+	{
+		nil,
+		0,
+		nil,
+		[]float64{1.23, 2.34},
+		[]float64{1.23, 2.34},
+	},
+	{
+		[]float64{},
+		0,
+		nil,
+		nil,
+		[]float64{},
+	},
+	{
+		[]float64{},
+		0,
+		nil,
+		[]float64{},
+		[]float64{},
+	},
+	{
+		[]float64{},
+		0,
+		nil,
+		[]float64{1.23, 2.34},
+		[]float64{1.23, 2.34},
+	},
+	{
+		[]float64{1.23},
+		1.23,
+		nil,
+		[]float64{2.34},
+		[]float64{2.34, 1.23},
+	},
+	{
+		[]float64{1.23, 2.34},
+		1.23,
+		[]float64{2.34},
+		[]float64{3.45},
+		[]float64{3.45, 1.23, 2.34},
+	},
+	{
+		[]float64{1.23, 2.34},
+		1.23,
+		[]float64{2.34},
+		[]float64{3.45, 4.56},
+		[]float64{3.45, 4.56, 1.23, 2.34},
+	},
+}
+
+func TestShiftAndUnshift(t *testing.T) {
+	for _, test := range shiftAndUnshiftTests {
+		t.Run("", func(t *testing.T) {
+			shifted, shift := pie.Shift(test.ss)
+			assert.Equal(t, test.shifted, shifted)
+			assert.Equal(t, test.shift, shift)
+			assert.Equal(t, test.unshift, pie.Unshift(test.ss, test.params...))
+		})
+	}
+}
diff --git a/v2/shuffle_test.go b/v2/shuffle_test.go
new file mode 100644
index 0000000..1e04812
--- /dev/null
+++ b/v2/shuffle_test.go
@@ -0,0 +1,48 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"math/rand"
+	"testing"
+)
+
+var shuffleTests = []struct {
+	ss       []float64
+	expected []float64
+	source   rand.Source
+}{
+	{
+		nil,
+		nil,
+		nil,
+	},
+	{
+		nil,
+		nil,
+		rand.NewSource(0),
+	},
+	{
+		[]float64{},
+		[]float64{},
+		rand.NewSource(0),
+	},
+	{
+		[]float64{12.3, 2.34, 4.56},
+		[]float64{2.34, 12.3, 4.56},
+		rand.NewSource(0),
+	},
+	{
+		[]float64{12.3},
+		[]float64{12.3},
+		rand.NewSource(0),
+	},
+}
+
+func TestShuffle(t *testing.T) {
+	for _, test := range shuffleTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, pie.Shuffle(test.ss, test.source))
+		})
+	}
+}
diff --git a/v2/sort_stable_using_test.go b/v2/sort_stable_using_test.go
new file mode 100644
index 0000000..1fe76b1
--- /dev/null
+++ b/v2/sort_stable_using_test.go
@@ -0,0 +1,46 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func stringShorter(a, b string) bool {
+	return len(a) < len(b)
+}
+
+var sortByLengthTests = []struct {
+	ss           []string
+	sortedStable []string
+}{
+	{
+		nil,
+		nil,
+	},
+	{
+		[]string{},
+		[]string{},
+	},
+	{
+		[]string{"foo"},
+		[]string{"foo"},
+	},
+	{
+		[]string{"aaa", "b", "cc"},
+		[]string{"b", "cc", "aaa"},
+	},
+	{
+		[]string{"zz", "aaa", "b", "cc"},
+		[]string{"b", "zz", "cc", "aaa"},
+	},
+}
+
+func TestSortStableUsing(t *testing.T) {
+	less := stringShorter
+	for _, test := range sortByLengthTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.sortedStable, pie.SortStableUsing(test.ss, less))
+		})
+	}
+}
diff --git a/v2/sort_test.go b/v2/sort_test.go
new file mode 100644
index 0000000..28ddb38
--- /dev/null
+++ b/v2/sort_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestSort(t *testing.T) {
+	for _, test := range sortTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.sorted, pie.Sort(test.ss))
+		})
+	}
+}
diff --git a/v2/sort_using_test.go b/v2/sort_using_test.go
new file mode 100644
index 0000000..991c50c
--- /dev/null
+++ b/v2/sort_using_test.go
@@ -0,0 +1,83 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+type carPointers []*car
+
+type car struct {
+	Name, Color string
+}
+
+func carPointerNameLess(a, b *car) bool {
+	return a.Name < b.Name
+}
+
+func carPointerColorLess(a, b *car) bool {
+	return a.Color < b.Color
+}
+
+var carPointersSortCustomTests = []struct {
+	ss                  carPointers
+	sortedStableByName  carPointers
+	sortedStableByColor carPointers
+}{
+	{
+		nil,
+		nil,
+		nil,
+	},
+	{
+		carPointers{},
+		carPointers{},
+		carPointers{},
+	},
+	{
+		carPointers{&car{"foo", "red"}},
+		carPointers{&car{"foo", "red"}},
+		carPointers{&car{"foo", "red"}},
+	},
+	{
+		carPointers{&car{"bar", "yellow"}, &car{"Baz", "black"}, &car{"foo", "red"}},
+		carPointers{&car{"Baz", "black"}, &car{"bar", "yellow"}, &car{"foo", "red"}},
+		carPointers{&car{"Baz", "black"}, &car{"foo", "red"}, &car{"bar", "yellow"}},
+	},
+	{
+		carPointers{&car{"bar", "yellow"}, &car{"Baz", "black"}, &car{"qux", "cyan"}, &car{"foo", "red"}},
+		carPointers{&car{"Baz", "black"}, &car{"bar", "yellow"}, &car{"foo", "red"}, &car{"qux", "cyan"}},
+		carPointers{&car{"Baz", "black"}, &car{"qux", "cyan"}, &car{"foo", "red"}, &car{"bar", "yellow"}},
+	},
+	{
+		carPointers{&car{"aaa", "yellow"}, &car{"aaa", "black"}, &car{"bbb", "yellow"}, &car{"bbb", "black"}},
+		carPointers{&car{"aaa", "yellow"}, &car{"aaa", "black"}, &car{"bbb", "yellow"}, &car{"bbb", "black"}},
+		carPointers{&car{"aaa", "black"}, &car{"bbb", "black"}, &car{"aaa", "yellow"}, &car{"bbb", "yellow"}},
+	},
+}
+
+func TestSortUsing(t *testing.T) {
+	isSortedUsing := func(ss carPointers, less func(a, b *car) bool) bool {
+		for i := 1; i < len(ss); i++ {
+			if less(ss[i], ss[i-1]) {
+				return false
+			}
+		}
+		return true
+	}
+
+	for _, test := range carPointersSortCustomTests {
+		t.Run("", func(t *testing.T) {
+			sortedByName := pie.SortUsing(test.ss, carPointerNameLess)
+			assert.True(t, isSortedUsing(sortedByName, carPointerNameLess))
+			sortedStableByName := pie.SortStableUsing(test.ss, carPointerNameLess)
+			assert.Equal(t, test.sortedStableByName, carPointers(sortedStableByName))
+
+			sortedByColor := pie.SortUsing(test.ss, carPointerColorLess)
+			assert.True(t, isSortedUsing(sortedByColor, carPointerColorLess))
+			sortedStableByColor := pie.SortStableUsing(test.ss, carPointerColorLess)
+			assert.Equal(t, test.sortedStableByColor, carPointers(sortedStableByColor))
+		})
+	}
+}
diff --git a/v2/stddev_test.go b/v2/stddev_test.go
new file mode 100644
index 0000000..de9bca7
--- /dev/null
+++ b/v2/stddev_test.go
@@ -0,0 +1,13 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestStddev(t *testing.T) {
+	assert.Equal(t, 0.0, pie.Stddev([]float64{}))
+	assert.Equal(t, 0.0, pie.Stddev([]float64{1}))
+	assert.Equal(t, 4.8587389053127765, pie.Stddev([]float64{10.0, 12.5, 23.3, 23.1, 16.5, 23.1, 21.2, 16.4}))
+}
diff --git a/v2/string_test.go b/v2/string_test.go
new file mode 100644
index 0000000..99a61b7
--- /dev/null
+++ b/v2/string_test.go
@@ -0,0 +1,13 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestString(t *testing.T) {
+	assert.Equal(t, "123", pie.String(123))
+	assert.Equal(t, "1.89", pie.String(1.89))
+	assert.Equal(t, "1.89", pie.String("1.89"))
+}
diff --git a/v2/strings_test.go b/v2/strings_test.go
new file mode 100644
index 0000000..ea78cd2
--- /dev/null
+++ b/v2/strings_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestStrings(t *testing.T) {
+	assert.Equal(t, []string{}, pie.Strings([]float64{}))
+
+	assert.Equal(t,
+		[]string{"92.384", "823.324", "453"},
+		pie.Strings([]float64{92.384, 823.324, 453}))
+}
diff --git a/v2/strings_using_test.go b/v2/strings_using_test.go
new file mode 100644
index 0000000..62f97be
--- /dev/null
+++ b/v2/strings_using_test.go
@@ -0,0 +1,44 @@
+package pie_test
+
+import (
+	"fmt"
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var stringsUsingTests = []struct {
+	ss        []float64
+	transform func(float64) string
+	expected  []string
+}{
+	{
+		nil,
+		func(s float64) string {
+			return "foo"
+		},
+		nil,
+	},
+	{
+		[]float64{},
+		func(s float64) string {
+			return fmt.Sprintf("%f!", s)
+		},
+		[]string{},
+	},
+	{
+		[]float64{6.2, 7.2, 8.2},
+		func(s float64) string {
+			return fmt.Sprintf("%.2f!", s)
+		},
+		[]string{"6.20!", "7.20!", "8.20!"},
+	},
+}
+
+func TestStringsUsing(t *testing.T) {
+	for _, test := range stringsUsingTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expected, pie.StringsUsing(test.ss, test.transform))
+		})
+	}
+}
diff --git a/v2/sub_slice_test.go b/v2/sub_slice_test.go
new file mode 100644
index 0000000..d2a1d9a
--- /dev/null
+++ b/v2/sub_slice_test.go
@@ -0,0 +1,108 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var subSliceTests = []struct {
+	ss       []float64
+	start    int
+	end      int
+	subSlice []float64
+}{
+	{
+		nil,
+		1,
+		1,
+		nil,
+	},
+	{
+		nil,
+		1,
+		2,
+		[]float64{0},
+	},
+	{
+		[]float64{},
+		1,
+		1,
+		nil,
+	},
+	{
+		[]float64{},
+		1,
+		2,
+		[]float64{0},
+	},
+	{
+		[]float64{1.23, 2.34},
+		-1,
+		-1,
+		nil,
+	},
+	{
+		[]float64{1.23, 2.34},
+		-1,
+		1,
+		nil,
+	},
+	{
+		[]float64{1.23, 2.34},
+		1,
+		-1,
+		nil,
+	},
+	{
+		[]float64{1.23, 2.34},
+		2,
+		0,
+		nil,
+	},
+
+	{
+		[]float64{1.23, 2.34},
+		1,
+		1,
+		nil,
+	},
+	{
+		[]float64{1.23, 2.34},
+		1,
+		2,
+		[]float64{2.34},
+	},
+	{
+		[]float64{1.23, 2.34},
+		1,
+		3,
+		[]float64{2.34, 0},
+	},
+	{
+		[]float64{1.23, 2.34},
+		2,
+		2,
+		nil,
+	},
+	{
+		[]float64{1.23, 2.34},
+		2,
+		3,
+		[]float64{0},
+	},
+	{
+		[]float64{1.23, 2.34, 0},
+		2,
+		3,
+		[]float64{0},
+	},
+}
+
+func TestSubSlice(t *testing.T) {
+	for _, test := range subSliceTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.subSlice, pie.SubSlice(test.ss, test.start, test.end))
+		})
+	}
+}
diff --git a/v2/sum_test.go b/v2/sum_test.go
new file mode 100644
index 0000000..a0e5fe8
--- /dev/null
+++ b/v2/sum_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestSum(t *testing.T) {
+	for _, test := range statsTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.sum, pie.Sum(test.ss))
+		})
+	}
+}
diff --git a/v2/top_test.go b/v2/top_test.go
new file mode 100644
index 0000000..06d3ef4
--- /dev/null
+++ b/v2/top_test.go
@@ -0,0 +1,15 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestTop(t *testing.T) {
+	for _, test := range topAndBottomTests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.top, pie.Top(test.ss, test.n))
+		})
+	}
+}
diff --git a/v2/unique_test.go b/v2/unique_test.go
new file mode 100644
index 0000000..764bf34
--- /dev/null
+++ b/v2/unique_test.go
@@ -0,0 +1,17 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestUnique(t *testing.T) {
+	for _, test := range uniqueTests {
+		t.Run("", func(t *testing.T) {
+			// We have to sort the unique slice because it is always returned in
+			// random order.
+			assert.Equal(t, test.unique, pie.Sort(pie.Unique(test.ss)))
+		})
+	}
+}
diff --git a/v2/unshift_test.go b/v2/unshift_test.go
new file mode 100644
index 0000000..6d8292d
--- /dev/null
+++ b/v2/unshift_test.go
@@ -0,0 +1,3 @@
+package pie_test
+
+// See shift_test.go
diff --git a/v2/values_test.go b/v2/values_test.go
new file mode 100644
index 0000000..4713c78
--- /dev/null
+++ b/v2/values_test.go
@@ -0,0 +1,21 @@
+package pie_test
+
+import (
+	"github.com/elliotchance/pie/v2"
+	"github.com/stretchr/testify/assert"
+	"sort"
+	"testing"
+)
+
+func TestValues(t *testing.T) {
+	assert.Equal(t, []currency(nil), pie.Values(currencies(nil)))
+
+	assert.Equal(t, []currency(nil), pie.Values(currencies{}))
+
+	values := pie.Values(isoCurrencies)
+	sort.Slice(values, func(i, j int) bool {
+		return values[i].NumericCode < values[j].NumericCode
+	})
+
+	assert.Equal(t, []currency{{36, -2}, {840, -2}}, values)
+}