pkg/list: add Haskell-style Take and Drop

In a post-slice world, these functions are a bit
more palatable than list.Slice if one of the
indices is open ended.

Adjusted slice signature and errors to be more
in line. Note that having less variation in error
messages reduces the burdon for applications that
would like to translate errors.

Change-Id: Id630d69aed9bfa88e71793a8068edb02987d6860
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3220
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index d9f8748..34979a8 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -144,6 +144,18 @@
 		test("list", `list.Avg("foo")`),
 		`_|_(cannot use "foo" (type string) as list in argument 1 to list.Avg)`,
 	}, {
+		test("list", `list.Drop([1, 2, 3, 4], 0)`),
+		`[1,2,3,4]`,
+	}, {
+		test("list", `list.Drop([1, 2, 3, 4], 2)`),
+		`[3,4]`,
+	}, {
+		test("list", `list.Drop([1, 2, 3, 4], 10)`),
+		`[]`,
+	}, {
+		test("list", `list.Drop([1, 2, 3, 4], -1)`),
+		`_|_(error in call to list.Drop: negative index)`,
+	}, {
 		test("list", `list.Max([1, 2, 3, 4])`),
 		`4`,
 	}, {
@@ -175,10 +187,10 @@
 		`[2,3]`,
 	}, {
 		test("list", `list.Slice([1, 2, 3, 4], -1, 3)`),
-		`_|_(error in call to list.Slice: negative slice index)`,
+		`_|_(error in call to list.Slice: negative index)`,
 	}, {
 		test("list", `list.Slice([1, 2, 3, 4], 3, 1)`),
-		`_|_(error in call to list.Slice: invalid slice index: 3 > 1)`,
+		`_|_(error in call to list.Slice: invalid index: 3 > 1)`,
 	}, {
 		test("list", `list.Slice([1, 2, 3, 4], 5, 5)`),
 		`_|_(error in call to list.Slice: slice bounds out of range)`,
@@ -195,6 +207,18 @@
 		test("list", `list.Sum("foo")`),
 		`_|_(cannot use "foo" (type string) as list in argument 1 to list.Sum)`,
 	}, {
+		test("list", `list.Take([1, 2, 3, 4], 0)`),
+		`[]`,
+	}, {
+		test("list", `list.Take([1, 2, 3, 4], 2)`),
+		`[1,2]`,
+	}, {
+		test("list", `list.Take([1, 2, 3, 4], 10)`),
+		`[1,2,3,4]`,
+	}, {
+		test("list", `list.Take([1, 2, 3, 4], -1)`),
+		`_|_(error in call to list.Take: negative index)`,
+	}, {
 		// Panics
 		test("math", `math.Jacobi(1000, 2000)`),
 		`_|_(error in call to math.Jacobi: big: invalid 2nd argument to Int.Jacobi: need odd integer but got 2000)`,
diff --git a/cue/builtins.go b/cue/builtins.go
index a2e2ff9..7cbd04f 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -663,29 +663,65 @@
 	},
 	"list": &builtinPkg{
 		native: []*builtin{{
+			Name:   "Drop",
+			Params: []kind{listKind, intKind},
+			Result: listKind,
+			Func: func(c *callCtxt) {
+				x, n := c.list(0), c.int(1)
+				c.ret, c.err = func() (interface{}, error) {
+					if n < 0 {
+						return nil, fmt.Errorf("negative index")
+					}
+
+					if n > len(x) {
+						return []Value{}, nil
+					}
+
+					return x[n:], nil
+				}()
+			},
+		}, {
+			Name:   "Take",
+			Params: []kind{listKind, intKind},
+			Result: listKind,
+			Func: func(c *callCtxt) {
+				x, n := c.list(0), c.int(1)
+				c.ret, c.err = func() (interface{}, error) {
+					if n < 0 {
+						return nil, fmt.Errorf("negative index")
+					}
+
+					if n > len(x) {
+						return x, nil
+					}
+
+					return x[:n], nil
+				}()
+			},
+		}, {
 			Name:   "Slice",
 			Params: []kind{listKind, intKind, intKind},
 			Result: listKind,
 			Func: func(c *callCtxt) {
-				a, i, j := c.list(0), c.int(1), c.int(2)
+				x, i, j := c.list(0), c.int(1), c.int(2)
 				c.ret, c.err = func() (interface{}, error) {
-					if i < 0 || j < 0 {
-						return nil, fmt.Errorf("negative slice index")
+					if i < 0 {
+						return nil, fmt.Errorf("negative index")
 					}
 
 					if i > j {
-						return nil, fmt.Errorf("invalid slice index: %v > %v", i, j)
+						return nil, fmt.Errorf("invalid index: %v > %v", i, j)
 					}
 
-					if i > len(a) {
+					if i > len(x) {
 						return nil, fmt.Errorf("slice bounds out of range")
 					}
 
-					if j > len(a) {
+					if j > len(x) {
 						return nil, fmt.Errorf("slice bounds out of range")
 					}
 
-					return a[i:j], nil
+					return x[i:j], nil
 				}()
 			},
 		}, {
diff --git a/go.sum b/go.sum
index 6c216a5..db97c52 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,7 @@
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/cockroachdb/apd/v2 v2.0.1 h1:y1Rh3tEU89D+7Tgbw+lp52T6p/GJLpDmNvr10UWqLTE=
 github.com/cockroachdb/apd/v2 v2.0.1/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/emicklei/proto v1.6.15 h1:XbpwxmuOPrdES97FrSfpyy67SSCV/wBIKXqgJzh6hNw=
 github.com/emicklei/proto v1.6.15/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
@@ -38,6 +39,7 @@
 github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/stretchr/testify v1.2.0 h1:LThGCOvhuJic9Gyd1VBCkhyUXmO8vKaBFvBsJ2k03rg=
 github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/xeipuuv/gojsonpointer v0.0.0-20170225233418-6fe8760cad35/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
diff --git a/pkg/list/list.go b/pkg/list/list.go
index f06f94f..9249f3f 100644
--- a/pkg/list/list.go
+++ b/pkg/list/list.go
@@ -22,8 +22,53 @@
 	"cuelang.org/go/cue"
 )
 
-// Slice extracts the consecutive elements from a list starting from position i
-// up till, but not including, position j, where 0 <= i < j <= len(a).
+// Drop reports the suffix of list x after the first n elements,
+// or [] if n > len(x).
+//
+// For instance:
+//
+//    Drop([1, 2, 3, 4], 2)
+//
+// results in
+//
+//    [3, 4]
+//
+func Drop(x []cue.Value, n int) ([]cue.Value, error) {
+	if n < 0 {
+		return nil, fmt.Errorf("negative index")
+	}
+
+	if n > len(x) {
+		return []cue.Value{}, nil
+	}
+
+	return x[n:], nil
+}
+
+// Take reports the prefix of length n of list x, or x itself if n > len(x).
+//
+// For instance:
+//
+//    Take([1, 2, 3, 4], 2)
+//
+// results in
+//
+//    [1, 2]
+//
+func Take(x []cue.Value, n int) ([]cue.Value, error) {
+	if n < 0 {
+		return nil, fmt.Errorf("negative index")
+	}
+
+	if n > len(x) {
+		return x, nil
+	}
+
+	return x[:n], nil
+}
+
+// Slice extracts the consecutive elements from list x starting from position i
+// up till, but not including, position j, where 0 <= i < j <= len(x).
 //
 // For instance:
 //
@@ -33,24 +78,24 @@
 //
 //    [2, 3]
 //
-func Slice(a []cue.Value, i, j int) ([]cue.Value, error) {
+func Slice(x []cue.Value, i, j int) ([]cue.Value, error) {
 	if i < 0 {
-		return nil, fmt.Errorf("negative slice index")
+		return nil, fmt.Errorf("negative index")
 	}
 
 	if i > j {
-		return nil, fmt.Errorf("invalid slice index: %v > %v", i, j)
+		return nil, fmt.Errorf("invalid index: %v > %v", i, j)
 	}
 
-	if i > len(a) {
+	if i > len(x) {
 		return nil, fmt.Errorf("slice bounds out of range")
 	}
 
-	if j > len(a) {
+	if j > len(x) {
 		return nil, fmt.Errorf("slice bounds out of range")
 	}
 
-	return a[i:j], nil
+	return x[i:j], nil
 }
 
 // MinItems reports whether a has at least n items.