proposal: Modify the tables proposal to allow tables of any dimension.

Change-Id: Ia4af1b21f6e60ac1ffcaacdf441cc254ad58a8e4
Reviewed-on: https://go-review.googlesource.com/17133
Reviewed-by: Andrew Gerrand <adg@golang.org>
diff --git a/design/6282-table-data.md b/design/6282-table-data.md
index 9772191..ff75f18 100644
--- a/design/6282-table-data.md
+++ b/design/6282-table-data.md
@@ -2,36 +2,30 @@
 
 Author(s): Brendan Tracey, with input from the gonum team
 
-Last updated: November 11th, 2015
+Last updated: November 24th, 2015
 
 Discussion at https://golang.org/issue/6282.
 
-## Note
-Note 2015/11/10: There are likely necessary updates based on discussion since the
-original reply by Nigel Tao.
-I felt it was best for the original commit to have the proposal as it was.
-Changes will be made as part of the proposal process and thus have a commit
-history.
-A further note on this topic will be included in the 'open issues' section.
-
-## Document Motivation:
-Issue 6282 was created in August of 2013, and despite the initial flurry of
-discussion, little has happened since.
-Norman Yarvin’s proposal is a lengthy one, and although complete, some of the
-resistance to its implementation comes from its complexity.
-This document proposes a smaller set of features while leaving room for future
-extension.
-These features solve the majority of the current problems using multi-
-dimensional data in Go, and leave some of the more advanced features for the
-future.
-This is thus a smaller change to the Go specification that can be grown over
-time if demand allows.
-
 ## Abstract
 
-This document proposes the addition of two-dimensional slices to Go.
-2-d slices, "tables", are accessed with two indices (rather than one), with each
-dimension bounds-checked for safety.
+This document proposes the addition of multi-dimensional slices to Go.
+It is proposed that this data structure be called a "table", and will be
+referred to as such from now on.
+Tables can be an underlying data type for many applications, including matrix
+manipulations, image processing, and 2-D gaming.
+
+A table represents an N-dimensional rectangular data structure with continuous
+storage.
+Each dimension of a table has a dynamically sized length and capacity.
+Arrays-of-arrays(-of-arrays-of...) are continuous in memory and rectangular but
+are not dynamically sized.
+Slice-of-slice(-of-slice-of...) are dynamically sized but are not
+continuous in memory and do not have a uniform length in each dimension.
+
+The table structure proposed here is a multi-dimensional slice of a
+multi-dimensional array.
+A table in N dimensions is accessed with N indices where each dimension is 
+bounds checked for safety.
 This proposal defines syntax for accessing and slicing, provides definitions for
 `make`, `len`, `cap`, `copy` and `range`, and discusses some additions to package
 reflect.
@@ -67,24 +61,27 @@
 numbers and has well-defined operations (multiply, determinant, cholesky
 decomposition).
 Tables, on the other hand, are merely a rectangular data container. A table can
-hold any data type and have none of the additional semantics of a matrix.
-Building a matrix on top of a table can (and should be) the job of a package. 
+be of any dimension, hold any data type and do not have any of the additional
+semantics of a matrix.
+A matrix can be constructed on top of a table in an external package.
 
 A rectangular data container can find use throughout the Go ecosystem.
-A partial list is 
+A partial list is
 
 1. Image processing: An image canvas can be represented as a retangle of colors.
-Here the ability to efficiently slice in multiple dimensions is important.  
+Here the ability to efficiently slice in multiple dimensions is important.
 2. Machine learning: Typically feature vectors are represented as a row of a
 matrix. Each feature vector has the same length, and so the additional safety of
 a full rectangular data structure is useful.
 Additionally, many fitting algorithms (such as linear regression) give this
 rectangular data the additional semantics of a matrix, so easy interoperability
 is very useful.
-3. 2D game development: Go is becoming increasingly popular for the development
+3. Game development: Go is becoming increasingly popular for the development
 of games.
+A player-specific section of a two or three dimensional space can be well
+represented by an n-dimensional array or table that has been sliced.
 Tile-based games are especially well represented as a slice, not only for
-depiction of the field of action, but the copy semantics are especially useful
+depicting the field of action, but the copy semantics are especially useful
 for dealing with sprites.
 
 Go is a great general-purpose language, and allowing users to slice a
@@ -94,10 +91,12 @@
 ### Language Workarounds
 There are several possible ways to emulate a rectangular data structure, each
 with its own downsides.
+This section discusses data in two dimensions, but similar problems exist for
+higher dimensional data.
 
 #### 1. Slice of slices
-Perhaps the most natural way to express a table in Go is to use a slice of
-slices (for example `[][]float64`).
+Perhaps the most natural way to express a two-dimensional table in Go is to use 
+a slice of slices (for example `[][]float64`).
 This construction allows convenient accessing and assignment using the
 traditional slice access
 
@@ -348,14 +347,14 @@
 ## Proposal
 
 The proposal is to add a new built-in generic type, a "table" into the language.
-It is a two-dimensional analog to a slice.
+It is a multi-dimensional analog to a slice.
 The term "table" is chosen because the proposed new type is just a data
 container.
 The term "matrix" implies the ability to do other mathematical operations (which
 will not be implemented at the language level).
 One may multiply a matrix; one may not multiply a table.
-Just as []T is shorthand for a slice, [,]T is shorthand for a table (as will be
-clear later).
+Just as `[]T` is shorthand for a slice, `[,]T` is shorthand for a two-dimensional 
+table, `[,,]T` a three-dimensional table, etc. (as will be clear later).
 
 ### Syntax (spec level specification)
 
@@ -365,86 +364,100 @@
 The elements are guaranteed to be stored in a continuous array, and are
 guaranteed to be stored in "row-major" order, which matches the existing layout
 of two-dimensional arrays in Go.
-Specifically, in a table t with m rows and n columns, the elements are laid out
-as
+Specifically, for a two-dimensional table t with m rows and n columns, the
+elements are laid out as
 
-	[t11, t12, … t1n, t21, t22, … t2n … , tm1, … tmn]
+	[t11, t12, ... t1n, t21, t22, ... t2n ... , tm1, … tmn]
 
-Guaranteeing a specific order allows code authors to reason about data layout
-for optimal performance.
-Row major is the only acceptable layout as tables can be considered as multi-
+Similarly, for a three-dimensional table with lengths m, n, and p, the data is 
+arranged as
+
+	[t111, t112, ... t11p, t121, ..., t12n, ... t211 ... , t2np, ... tmnp]
+
+Row major is the only acceptable layout. Tables can be constructed as multi-
 dimensional arrays which have been sliced, and the spec guarantees that multi-
 dimensional arrays are in row-major order.
+Furthermore, guaranteeing a specific order allows code authors to reason about
+data layout for optimal performance.
 
 #### Using make:
-A new table (of generic type) may be allocated by using the make command with
-two mandatory length arguments followed by two optional capacity arguments.
-If either capacity is present both must be.
-If no capacity arguments are present, they are defaulted to the length
-arguments.
-The first length and capacity argument are for the first dimension, and the
-second length and capacity are for the second dimension.
-These act like the length and capacity for slices.
+A new N-dimensional table (of generic type) may be allocated by using the make
+command with a mandatory argument of a [N]int specifying the length in each
+dimension, followed by an optional [N]int specifying the capacity in each
+dimension.
+If the capacity argument is not present, each capacity is defaulted to its
+respective length argument.
+These act like the length and capacity for slices, but on a per-dimension basis.
 The table will be filled with the zero value of the type
 
-	s := make([,]T, r, c, maxr, maxc)
-	t := make([,]T, r, c)
+	s := make([,]T, [2]int{m, n}, [2]int{maxm, maxn})
+	t := make([,]T, [2]int{m, n})
+	s2 := make([,,]T, [3]int{m, n, p}, [3]int{maxm, maxn, maxp})
+	t2 := make([,,]T, [3]int{m, n, p})
 
 Calling make with a zero length or capacity is allowed, and is equivalent to
 creating an equivalently sized multi-dimensional array and slicing it.
 In the following code
 
-	u := make([,]float32, 0, 6)
-	v := [0][6]float32{}
-	w := v[0:0, 0:6]
+	u := make([,,,]float32, [4]int{0, 6, 4, 0})
+	v := [0][6][4][0]float32{}
+	w := v[0:0, 0:6, 0:4, 0:0]
 
 u and w have the same behavior.
-Specifically, the length and capacities for both are 0 and 6 in the two
+Specifically, the length and capacities for both are 0, 6, 4, and 0 in the
 dimensions, and the underlying data array contains 0 elements.
 
 #### Table literals
 A table literal can be constructed using nested braces
 
 	u := [,]T{{x, y, z}, {a, b, c}}
+	v := [,,]T{{{1, 2, 3, 4}, {5, 6, 7, 8}}, {{9, 10, 11, 12}, {13, 14, 15, 16}}}
 
-The allocated table will have a number of rows equal to the number of sets of
-braces, and will have a number of columns equal to the number of elements within
+The size of the table will depend on the size of the brace sets, outside in.
+For example, in a two-dimensional table the number of rows is equal to the number
+of sets of braces, the number of columns is equal to the number of elements within
 each set of braces.
-For example, u has two rows and three columns.
-It is a compile error if the inner braces do not all contain the same number of
+In a three-dimensional table, the length of the first dimension is the number 
+of sets of brace sets, etc.
+Above, u has length [2, 3], and v has length [2, 2, 4].
+It is a compile-time error if each brace layer does not contain the same number of
 elements.
+Like normal slices and arrays, key-element literal construction is allowed.
+For example, the two following constructions yeild the same result
+
+	[,]int{{0:1, 2:0},{1:1, 2:0}, {2:1}}
+	[,]int{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}
 
 ### Access/ Assignment
-An element of a table can be accessed with [row,column] syntax, and can be
+An element of a table can be accessed with [idx0, idx1, ...] syntax, and can be
 assigned to similarly.
 
 	var v T
-	v = t[r,c]
-	t[r,c] = v
+	v = t2[m,n]
+	t3[m,n,p] = v
 
-If r is negative or if r >= the length in the first dimension, a runtime panic
-occurs, and likewise with c and the second dimension.
+If any index is negative or if it is greater than or equal to the length
+in that dimension, a runtime panic occurs.
 Other combination operators are valid (assuming the table is of correct type)
 
-	t := make([,]float64, 2,3)
+	t := make([,]float64, [2]int{2,3})
 	t[1,2] = 6
 	t[1,2] *= 2   // Now contains 12
 	t[3,3] = 4    // Runtime panic, out of bounds (possible compile-time with
 	              // constants)
 
 ### Slicing
-A table can be sliced using the normal 2 or 3 index slicing rules on each side
-of the comma i:j or i:j:k.
-The same panic rules as slices apply (0 <= i <= j <= k, must be less than the
+A table can be sliced using the normal 2 or 3 index slicing rules in each
+dimension, `i:j` or `i:j:k`.
+The same panic rules as slices apply (`0 <= i <= j <= k`, must be less than the
 capacity).
-Like slices, this would update the length and capacity in the respective
+Like slices, this updates the length and capacity in the respective
 dimensions
 
-	a := make([,]int, 10, 2, 10, 15)
+	a := make([,]int, [2]int{10, 2}, [2]int{10, 15})
 	b := a[1:3, 3:5:6]
 
-Also in-line with slices, a multi-dimensional array may be sliced to create a
-table.
+A multi-dimensional array may be sliced to create a table.
 In
 
 	array := [10][5]int
@@ -453,8 +466,8 @@
 t is a table with lengths 4 and 2, capacities 5 and 10, and a stride of 5.
 
 ### Length / Capacity
-Like slices, the len and cap built-ins can be used on a table.
-Len and cap take in a table and return a [2]int representing the lengths/
+Like slices, the `len` and `cap` built-in functions can be used on a table.
+Len and cap take in a table and return a [N]int representing the lengths/
 capacities in the dimensions of the table.
 
 	lengths := len(t)    // lengths is a [2]int
@@ -464,14 +477,15 @@
 
 #### Discussion:
 This behavior keeps the natural definitions of len and cap.
-There are three possible syntax choices
+There are four possible syntax choices
 
-	lengths := len(t)     // returns [2]int
+	lengths := len(t)     // returns [N]int
 	length := len(t, 0)   // returns the length of the table along the first
 	                      // dimension
-	rows, cols := len(t)
+	len(t[0,:]) or len(t[ ,:]) // returns the length along the second dimension
+	m, n, p, ... := len(t)
 
-The first behavior is preferable to the other two.
+The first behavior is preferable to the other three.
 In the first syntax, it is easy to get any particular dimension (access the
 array) and if the array index is a constant, it is verifiable and optimizable at
 compile-time.
@@ -479,24 +493,30 @@
 
 	len(x) == len(y)
 
-Third, this behavior naturally extends to higher dimensional tables, as length
-can be extended to return an [n]int.
+Finally, this behavior interacts well with make 
+
+	t2 := make([,,]T, len(t), cap(t))
 
 The second representation seems strictly worse than the first representation.
 While it is easy to obtain a specific dimension length of the table, one cannot
 compare the full table lengths directly.
 One has to do
-	len(x,0) == len(y, 0) && len(x,1) == len(y,1)
+
+	len(x,0) == len(y, 0) && len(x,1) == len(y,1) && len(x,2) == len(y,2) && ...
+
 Additionally, now the call to length requires a check that the second argument
-is 0 or 1, and may panic if that check fails.
+is less than the dimension of the table, and may panic if that check fails.
 There doesn’t seem to be any benefit gained by allowing this failure.
 
-The third option will always succeed, but again, it’s hard to compare the full
+The third syntax possibility has the same weaknesses as the second. It's hard to
+compare table sizes, it can possibly fail at runtime, and does not mesh with make.
+
+The fourth option will always succeed, but again, it’s hard to compare the full
 lengths of the tables
 
-	rx, cx := len(x)
-	ry, cy := len(y)
-	rx == ry && cx == cy
+	mx, nx, px, ... := len(x)
+	my, ny, py, ... := len(y)
+	rx == ry && cx == cy && px == py && ...
 
 Additionally, it’s hard to get any one specific length.
 Such an ability is useful in for loops, for example
@@ -509,21 +529,21 @@
 
 	r1, r2, r3, r4, r5 := len([,,,,]int{})
 
-is pretty silly. It would be much better to return a [5]int
+is pretty silly. It would be much better to return a `[5]int`.
 
 ### Copy
 There are two cases in copy for tables.
 The first is a table-table copy in which copy takes in two tables and returns a
-[2]int specifying the number of elements that were copied in each dimension.
+[N]int specifying the number of elements that were copied in each dimension.
 
-	n := copy(dst, src)   // n is a [2]int
+	n := copy(dst, src)   // n is a [N]int
 
-Copy will copy all of the elements in the subtable from the first row to
-min(len(dst)[0], len(src)[0]) and the first column to
-min(len(dst)[1], len(src)[1]).
+Copy will copy all of the elements in the subtable from the first dimension to
+`min(len(dst)[0], len(src)[0])` the second dimension to
+`min(len(dst)[1], len(src)[1])`, etc.
 
-	dst := make([,]int, 6, 8)
-	src := make([,]int, 5, 10)
+	dst := make([,]int, [2]int{6, 8})
+	src := make([,]int, [2]int{5, 10})
 	n := copy(dst, src) // n == [2]int{5, 8}
 	fmt.Println("All destination elements were overwritten:", n == len(dst))
 
@@ -537,7 +557,7 @@
 In the case of slice-table copy, the element-type must be the same.
 
 	slice := []int{0, 0, 0, 0, 0}
-	table := [,]int{{1,2,3} , {4,5,6}, {7,8,9}, {10,11,12}}
+	table := [,]int{{1,2,3}, {4,5,6}, {7,8,9}, {10,11,12}}
 	copy(slice, table[1,:])    // Copies all the whole second row of the table
 	fmt.Println(slice)  // prints [4 5 6 0 0]
 	copy(table[1:, 2], slice[1:])  // copies elements 1:4 of the slice into the
@@ -550,7 +570,7 @@
 The table-table copy seems like the only reasonable extension to copy for tables.
 
 The single dimension copy is required because it is very common to want to
-extract data from a specific row or column of a table.
+extract data along a specific dimension of a table.
 It does increase the complexity of copy, as there needs to be an implementation
 of runtime·copyn that handles strided copying (more than just memmove).
 However, this implementation is already required for a table-table copy, and the
@@ -573,11 +593,13 @@
 and a runtime panic will occur if the fixed integer is out of bounds of the
 table in that dimension.
 To help with legibility, gofmt will format such that there is a space between
-the comma and the bracket when the fixed index is omitted, i.e. [:, ] and [ ,:],
-not [:,] and [,:].
+the comma and the bracket when the fixed index is omitted, i.e. `[:, ]` and 
+`[ ,:]`, not `[:,]` and `[,:]`.
 
 #### Examples
 
+Two-dimensional tables.
+
 	table := [,]int{{1,2,3} , {4,5,6}, {7,8,9}, {10,11,12}}
 	for i, v := range table[2,:]{
 		fmt.Println(i, v)  // i ranges from 0 to 2, v will be 7,8,9
@@ -610,17 +632,27 @@
 		}
 	}
 
+Higher-dimensional tables
+
+	table := [,,]int{{{1, 2, 3, 4}, {5, 6, 7, 8}}, {{9, 10, 11, 12}, {13, 14, 15, 16}}}
+	for i, v := range [1,:, 3]{
+		fmt.Println(i, v) // i ranges from 0 to 1, v will be 12, 16
+	}
+	for i := range [ , ,:]{
+		fmt.Println(i) // i ranges from 0 to 3
+	}
+
 #### Discussion
 This description of range mimics that of slices.
 It permits a range clause with one or two values, where the type of the second
 value is the same as that of the elements in the table.
 This is far superior to ranging over the rows or columns themselves (rather than
 the elements in a single row or column).
-Such a proposal (where the value is []T instead of just T) would have O(n) run
+Such a proposal (where the value is `[]T` instead of just `T`) would have O(n) run
 time in the length of the table dimension and could create significant extra
 garbage.
 
-The option to omit a specific row or column is necessary to allow for nice
+The option to omit specific dimensions is necessary to allow for nice
 behavior when the length of the table is zero in the relevant dimension.
 One may sum the elements in a slice as follows:
 
@@ -641,14 +673,14 @@
 	}
 
 Were it mandatory to specify a particular column, one would have to replace
-t[:,] with t[:,0] in the first range clause.
-If len(t,0) == 0, this would panic.
+`t[:,]` with `t[:,0]` in the first range clause.
+If `len(t,0) == 0`, this would panic.
 It would thus be necessary to add in an extra length check before the range
 statements to avoid such a panic.
 
 There is an argument about the legibility of the syntax, however this should not
 be a problem for most programmers.
-There are four ways one may range over a table:
+There are four ways one may range over a two-dimensional table:
 
 1. `for i := range t[:, ]`
 2. `for i := range t[ ,:]`
@@ -679,8 +711,8 @@
 
 	for i := range t[:,_]
 
-This has the benefit that the programmer must specify something on both sides of
-the comma, and this usage matches the spirit of underscore in that "the specific
+This has the benefit that the programmer must specify something for every
+dimension, and this usage matches the spirit of underscore in that "the specific
 column doesn’t matter".
 This was rejected for fear of overloading underscore, but remains a possibility
 if such overloading is acceptable.
@@ -696,8 +728,6 @@
 ## Compatibility
 
 This change is fully backward compatible with the Go1 spec.
-It has also been designed to be compatible with a future extension to
-n-dimensional tables.
 
 ## Implementation
 
@@ -705,36 +735,43 @@
 
 	type Table struct {
 		Data       uintptr
-		Stride     int
-		Len        [2]int
-		Cap        [2]int
+		Stride     [N-1]int
+		Len        [N]int
+		Cap        [N]int
 	}
 
-Access and assignment can be performed using the stride.
-t[i,j] gets the element at i*stride + j in the array pointed to by the Data
-uintptr.
-When a new table is allocated, Stride is set to Cap[0] (for row major).
+As a special case, a two-dimensional table would have the `Stride` field as an
+`int` instead of a `[1]int`.
+
+Access and assignment can be performed using the strides.
+For a two-dimensional table, `t[i,j]` gets the element at `i*stride + j` in the
+array pointed to by the Data uintptr.
+More generally, `t[i0,i1,...,iN-2,iN-1]` gets the element at
+
+	i0 * stride[0] + i1 * stride[1] + ... + iN-2 * stride[N-2] + iN-1
+
+When a new table is allocated, `Stride` is set to `Cap[N-1]`.
 
 A table slice is as simple as updating the pointer, lengths, and capacities.
 
-	t[a:b:c, d:e:f]
+	t[i0:j0:k0, i1:j1:k1, ..., iN-1:jN-1:kN-1]
 
-causes Data to update to a*Stride + d, Len[0] = b-a, Len[1] = e-d, Cap[0] = c-a,
-Cap[1] = f-d, and Stride is unchanged.
+causes `Data` to update to the element indexed by `[i0,i1,...,iN-1]`,
+`Len[d] = jd - id`, `Cap[d] = kd - id`, and Stride is unchanged.
 
 ### Reflect
 
-Package reflect will add reflect.TableHeader (like reflect.SliceHeader and
+Package reflect will add reflect.TableHeader2 (like reflect.SliceHeader and
 reflect.StringHeader).
 
-	type TableHeader struct {
+	type TableHeader2 struct {
 		Data       uintptr
 		Stride     int
 		Len        [2]int
 		Cap        [2]int
 	}
 
-The same caveats can be placed on TableHeader as the other Header types.
+The same caveats can be placed on TableHeader2 as the other Header types.
 If it is possible to provide more guarantees, that would be great, as there
 exists a large body of C libraries written for numerical work, with lots of time
 spent in getting the codes to run efficiently.
@@ -747,6 +784,8 @@
 support the new type (MakeTable, TableOf, SliceTable, etc.).
 The exact signatures of these methods can be decided upon at a later date.
 
+### Implementation Schedule
+
 Help is needed to determine the when and who for the implementation of this
 proposal.
 The gonum team would translate the code in gonum/matrix, gonum/blas,
@@ -759,31 +798,6 @@
 they will be added), but that they provide additional complications and, in my
 opinion, do not pull their weight for an initial implementation.
 
-### Full multi-dimensional slices
-The most controversial part of this proposal is the decision to only add
-two-dimensional tables to the language.
-The reason for this is to minimize the immediate changes to the language and to
-keep the initial implementation small.
-While many of the language behaviors (allocation, slicing, length, copy, range,
-etc.) extend quite nicely to higher dimensions, the implementation is quite a
-bit more complex (Norman’s proposal used product notation for example), and the
-language extensions needed in reflect are less straight-forward (the discussion
-in Norman’s proposal about SliceHeader6D for example).
-Two-dimensional tables are much more common in numerical work, and in my
-experience, higher dimensional tables are mostly used for storing data rather
-than manipulating it.
-Accesses and assignments are not in the inner-loop of computation, thus nested
-slices could suffice.
-
-That said, this proposal was designed to allow extension to higher-dimensional
-tables without backward-incompatible changes.
-There are implementation choices which would need to be made, for example, would
-there be one "multi-dimensional"  table type, or would each dimensional type
-have its own internal representation (length represented as []int vs. [N]int for
-example).
-These choices would have to be made in a future proposal to extend tables to
-higher dimensions.
-
 ### Down-slicing
 Norman’s proposal suggested the allowance of "down-slicing", where one could
 turn, say, a table into a slice with a single element slicing operation
@@ -794,7 +808,7 @@
 In my opinion, this feature would be nice, but it either requires one of three
 things
 
-1. Asymmetry in spec (table[1,2:10] is allowed but not table[2:10,1])
+1. Asymmetry in spec (`t[1,2:10]` is allowed but not `t[2:10,1]`)
 2. Modification of the slice implementation to add a stride field
 3. Have a "1-D table" type which is the same as a slice but has a stride
 
@@ -817,25 +831,25 @@
 ### Append
 Like Norman’s proposal, this proposal does not allow append to be used with
 tables. This is mainly a matter of syntax. Can you append a table to a table or
-just add a row/column at a time. What would the syntax be? Again, better to leave
-for later.
+just add to a single dimension at a time? What would the syntax be? 
+Again, better to leave for later.
 
 ### Arithmetic Operators
 Some have called for tables to support arithmetic operators (+, -, *) to also
-work on [,]Numeric (int, float64, etc.), for example
+work on `[,]Numeric` (`int`, `float64`, etc.), for example
 
-	a := make([,], 1000, 3000)
-	b := make([,], 3000, 2000)
+	a := make([,]float64, 1000, 3000)
+	b := make([,]float64, 3000, 2000)
 	c := a*b
 
 While operators can allow for very succinct code, they do not seem to fit in Go.
 Go’s arithmetic operators only work on numeric types, they don’t work on slices.
 Secondly, arithmetic operators in Go are all fast, whereas the operation above
-is many more orders of magnitude more expensive than a floating point multiply.
+is many orders of magnitude more expensive than a floating point multiply.
 Finally, multiplication could either mean element-wise multiplication, or
 standard matrix multiplication.
 Both operations are needed in numerical work, so such a proposal would require
-additional operators to be added (such as .*).
+additional operators to be added (such as `.*`).
 Especially in terms of clock cycles per character, `c.Mul(a,b)` is not that bad.
 
 ## Conclusion
@@ -865,15 +879,10 @@
 ## Open issues
 Since this was originally proposed, a few issues have come to light.
 
-1. It seems Go would want to add tables of any size.
-The proposal was designed with this as a future outcome and can easily be
-modified.
-There are no significant changes in the proposal in the higher-dimensional
-extension.
-2. Given the desire for 'all-at-once' changes rather than incremental changes
+1. Given the desire for 'all-at-once' changes rather than incremental changes
 (which was the belief at the time of writing), it's worth thinking about
 down-slicing.
 This would likely be option 1 in the non-goals.
-3. In the discussion, it was mentioned that adding a TableHeader is a bad idea.
+2. In the discussion, it was mentioned that adding a TableHeader is a bad idea.
 This can be removed from the proposal, but some other mechanism should be added
-that allows data in tables to be passed to C.
+that allows data in tables to be passed to C.
\ No newline at end of file