Thanks for your reply Ian.
On Jul 15, 2014, at 6:58 AM, Ian Lance Taylor <
ia...@golang.org> wrote:
> On Mon, Jul 14, 2014 at 11:24 PM, Brendan Tracey
> <
tracey....@gmail.com> wrote:
>>
>> The proposal was released to the Go community at large in March during the
>> 1.3 feature freeze. Since then, I have addressed all comments from the
>> community. As far as I am aware, the outstanding objections are either the
>> mere fact that it adds to the language at all or that it does not include
>> one or more of the non-goals described in the proposal.
>
> Here are the things that trouble me personally about the proposal.
>
> In the language it's barely possible to justify having only
> single-dimensional arrays and slices. I think that if we had
> two-dimensional matrixes, we need to have a clear understanding of how
> we would introduce multi-dimensional matrixes. I understand that your
> proposal explicitly decides not to do that, but it seems like such an
> obvious request.
We thought about including them in the proposal, and, in fact, a lot of the proposed behaviors extend nicely.
make([,,]bool, i, j, k, capi, capj, capk)
len returns an [n]int
range still has only one : (range t[i,:,k])
I think the only non-obvious extension to the syntax is the question of whether one may use copy between a [,]int and a [,,,,]int (for example). If I am wrong in that assertion, please let me know.
Implementation, on the other hand, is another issue. Is it
type Table struct{
Data uintptr
Stride int
Len [n]int
Cap [n]int
}
where n is generated at compile time? Is it
type Table struct{
Data uintptr
Stride int
Len []int
Cap []int
}
where there is only one type for all of the tables? This simplifies implementation, but is probably slower, and then to return [n]int you’d have to change the []int into [n]int. In addition, accessing the additional data field is more complicated code.
During the original public discussion, several people made the comment you did. However, no one provided examples of code that would be improved significantly by multi-dimensional tables. I don’t think any of the proposers are opposed to higher-dimensional tables, but it seemed better to keep things simple, and wait until real need is demonstrated before adding to the proposal. If I am wrong in that, the proposal can be modified, though I would need help in deciding implementation issues, as I don’t understand the complexities of having a bunch of different types.
>
> Your implementation of the slice operator says that the stride field
> does not change. This does let you pick out a contiguous submatrix
> from a matrix. But once the concept of the stride exists, it seems
> likely that people will want to be able to manipulate it. Many matrix
> operations, particularly in image processing, involve operations with
> different strides. Should there be direct support for this?
Could you provide some examples? The only one I can think of for matrices would be computing the trace of a matrix. I don’t see how modifying the stride could work as expected, unless you also allow people to change the length (and capacity). This would seem difficult to do correctly, especially given that the current table may have been a sliced version of a previous table.
>
> The range clause seems frankly awkward. Did you consider
> for i, j, v := range m
> That is, the range would iterate over the entire matrix, returning the
> row, column, and value. You can still iterate over a single row or
> column by taking a slice; you will still get both a row and column
> index, but one of the values would not change.
I understand that the syntax seems awkward at first glance, but I personally like it after using it a couple of times.
We did consider the syntax you suggest. In my experience, ranging over a row or column is much more common than ranging over all of the elements (for example, the BLAS libraries don’t have any functions that range over all elements directly). As a result, this case should be easy. If we look at the 3- element syntax, for matrix multiply it would look something like
for i := range a[:,0:1]{
for _, k, va := range a[i:i+1,:]{
for _, j, vb := range b[k:k+1, :]{
c[i,j] += va * vb
}
}
}
This seems to have a couple of problems. First, I would argue that it at least doesn’t look any better than the proposed syntax. Secondly, it doesn’t solve the case where ‘a’ has zero length in the second dimension (the above code will panic, and there is no way to write the code to accept zero length tables without additional checks). Third, now the idea of choosing a specific row is no longer compile-time verifiable. The user must ensure they have i:i+1 (and not something else), which on it’s own isn’t that bad, but I can imagine users putting two variables there. Lastly, I feel that it’s possible to write much more mind-warping code with the full table range. Seeing something like
for i, j, v := range a[2:7, :]{
for k, l, w := range b[2:4, 7:9]{
// Some operation
}
}
starts to get hard to figure out what’s going on.
>
> In more or less the same vein, did you consider having len, cap, and
> copy return multiple values, rathern [2]int? I guess that does make
> it harder to use them in expressions, though.
This is talked about in the discussion for length. It’s harder to use them in expressions, it’s harder to compare all of the lengths of tables, and it doesn’t extend well to higher dimensional tables.
>
> The reflect.SliceHeader type was a mistake, let's not introduce
> reflect.TableHeader.
Okay. I thought such an addition would be necessary.
> If you look at the C roots of Go, then, conceptually slices are
> bounded pointers, and arrays are the memory to which they refer. This
> is confused somewhat in the language because the make function, and
> composite literals, quietly create the array in the background and
> then give you the bounded pointer (the slice). You are conceptually
> introducing a strided slice, in which each access requires two
> indexes. But as I mentioned above the stride is implicit, and there
> is no way to set it explicitly. I wonder if there is some way to
> separate out the notion of striding, since that seems to be the key
> idea.
Possibly. I think there are really two ideas. The first is the strided slice, and the second is the idea that as far as the programmer is concerned, the data is laid out in a rectangle. In the discussion of down-slicing, I partially discuss the idea of having strided slices. I don’t really see how strided slices would help solve the problems we discuss here. Maybe I’m not understanding what you are suggesting, but when I think of a strided slice, I think of s[6] actually giving me 6*stride of the real underlying data array. For tables, we need to access both with a stride of 1 and with a stride of n. Furthermore, it doesn’t solve the issues of row-major vs. column-major, and it doesn’t help with bounds checking.
Thanks again for your detailed reply. Further thoughts are appreciated.