0

In the below main() code:

package main

import (
    "fmt"

    "github.com/myhub/cs61a/poetry"
)

func main() {

    p := poetry.NewPoem([][]string{
        {
            "And from my pillow, looking forth by light",
            "Of moon or favoring stars, I could behold",
            "The antechapel where the statue stood",
            "Of Newton, with his prism and silent face,",
            "The marble index of a mind forever",
            "Voyaging through strange seas of thought, alone.",
        },
        {
            "inducted into Greek and Christian",
            "modes of thinking, must take a longer way around.",
            "Like children born into love, they exult in the here",
            "who have felt separation and grave misgivings, they",
            "and of humanity, and of God. Great literature, like",
            "struggles to reconcile suffering with faith in the ",
        },
    })

    fmt.Printf("%T\n", p[0])

}

p[0] works fine by pointing to first stanza using below function constructor:

package poetry

type Line string
type Stanza []Line
type Poem []Stanza

func NewPoem(s [][]string) Poem {
    var poem Poem
    for _, stanza := range s {
        var newStanza Stanza
        for _, line := range stanza {
            newStanza = append(newStanza, Line(line))
        }
        poem = append(poem, newStanza)
    }

    return poem
}

If, NewPoem() returns value of type *Poem, as shown below:

package poetry

type Line string
type Stanza []Line
type Poem []Stanza

func NewPoem(s [][]string) *Poem {
    var poem Poem
    for _, stanza := range s {
        var newStanza Stanza
        for _, line := range stanza {
            newStanza = append(newStanza, Line(line))
        }
        poem = append(poem, newStanza)
    }

    return &poem
}

then, p[0] in main() gives below error:

     Invalid operation: p[0] (type *poetry.Poem does not support indexing)

Why pointer to slice of slice of strings does not support p[0] syntax?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
overexchange
  • 15,768
  • 30
  • 152
  • 347
  • @CeriseLimón Sorry about that.... Query edited – overexchange May 29 '20 at 22:58
  • 1
    For what it's worth, what's copied when you pass the slice is a pretty small structure referring to the contents (a pointer, length, and capcity). The contents aren't copied. If you're passing a pointer for efficiency, you may not need to. – twotwotwo May 29 '20 at 23:02
  • 2
    The whole question of when a pointer helps comes up a lot, longer answer [here](https://stackoverflow.com/questions/23542989/pointers-vs-values-in-parameters-and-return-values/23551970). The direct answer is that you need to dereference the pointer (*p) before indexing into it; see https://play.golang.org/p/KFArF0BCbsb – twotwotwo May 29 '20 at 23:05

3 Answers3

6

Why pointer to slice of slice of strings does not support p[0] syntax?

Short answer: because the language spec does not allow it.

Longer answer: because pointers to slices are rarely used, and why complicate the language with something that doesn't benefit many? If in the rare case you do need it, just dereference the pointer and index the slice.

Note that indexing or slicing an array pointer is allowed by the spec, because when arrays are used, pointers to arrays are more often and more useful (than pointers to slices). Read more about it here: Slicing a slice pointer passed as argument

icza
  • 389,944
  • 63
  • 907
  • 827
2

Why pointer to slice of slice of strings does not support p[0] syntax?

Because a pointer to a slice of (anything) is not a type that the language defines to support indexing. Here's the spec:

A primary expression of the form a[x] denotes the element of the array, pointer to array, slice, string or map a indexed by x. The value x is called the index or map key, respectively. The following rules apply:

Source: https://golang.org/ref/spec#Index_expressions

It goes on and explicitly defines how indexing works for each of the supported types, and ends by saying that Otherwise a[x] is illegal.

So basically you're trying to do something that the language doesn't allow.


As for why, and talking a bit about your design, pointers to slices rarely make sense.

First, when you pass a slice around, the amount of data moving is absolutely minimal (it's just a slice header, which is generally a dozen bytes), so there's no visible gain compared to passing a pointer.

Then, in your comments in the question you say, "it looks as a better abstraction to provide a pointer to Poem type." It really depends on what you want to do with the type Poem. With the few details you gave, I'd argue that it's actually a worse design choice to pass *Poem around rather than just Poem, since you gain nothing with the pointer, and you actually complicate things. For example, you can't use indexing (p[0])...


In a comment to this answser, you asked: do you see [][]string{...} passing to NewPoem() a good approach?.

Like I mentioned in a comment, we don't have a lot of details, so it's hard to say. But currently, you can do this to create a Poem:

p := poetry.Poem{
    {
        "And from my pillow, looking forth by light",
        "Of moon or favoring stars, I could behold",
        "The antechapel where the statue stood",
        "Of Newton, with his prism and silent face,",
        "The marble index of a mind forever",
        "Voyaging through strange seas of thought, alone.",
    },
    {
        "inducted into Greek and Christian",
        "modes of thinking, must take a longer way around.",
        "Like children born into love, they exult in the here",
        "who have felt separation and grave misgivings, they",
        "and of humanity, and of God. Great literature, like",
        "struggles to reconcile suffering with faith in the ",
    },
}

fmt.Printf("%T\n", p[0])

No need for loops or appends or a NewPoem function or anything to create a Poem directly from strings. Just use the type Poem{...} directly!

If you need to do something different like creating a Poem by reading data from, say, a file, you could maybe create a function like this instead:

package poetry

func ReadFrom(rd io.Reader) Poem { ... }

But for simply creating a Poem in the code, directly, there's no need to complicate things.

Bruno Reis
  • 37,201
  • 11
  • 119
  • 156
  • I want to find number of stanzas, number of lines, number of vowels, consonants, punctuations etc...In the above code, do you see `[][]string{...}` passing to `NewPoem()` a good approach? – overexchange May 29 '20 at 23:36
  • @overexchange — in terms of design, those are all "read only" operations, so you don't need to modify the poem after it's created. So you can make `Poem` an immutable type (which means a ton of extra benefits that you get "for free"). That's yet another reason for you to not use a pointer. – Bruno Reis May 29 '20 at 23:38
  • Yes, pointer type is passed or return for mutating the data. I would append a stanza later – overexchange May 29 '20 at 23:39
  • @overexchange — I'll edit my answer to talk about the `[][]string{...}` thing, as a comment is probably not the best place. – Bruno Reis May 29 '20 at 23:40
  • @overexchange — even if you want to append a stanza later, you may still not need a pointer. You could instead use something like this: `func (p Poem) AddStanza(s Stanza) Poem {...}`, that will return a new, immutable poem, with the extra stanza. And then use it for example like this: `var p Poem; ...; p = p.AddStanza(...)` – Bruno Reis May 29 '20 at 23:46
  • And just for clarity, I'm not saying that it's necessarily better to make it immutable and avoid pointers at all costs. All I'm saying is that, so far, I don't really see a benefit to using a pointer, while I do see benefits to avoiding it. – Bruno Reis May 29 '20 at 23:47
0

Why pointer to slice of slice of strings does not support p[0] syntax?

Because it's a pointer it has no indexable elements inside like array or slice. If you wish index access to Stanza's you may dereference the pointer (thus gaining a slice) and then access 0'th element in the slice:

fmt.Printf("%T\n", (*p)[0])

Output:

main.Stanza

Or a whole code of an example: https://play.golang.org/p/7abmjp6AxA4

BUT. Do you really need a pointer to a slice? Slices in Golang are very lightweight structures - the consists of 3 64 bit values: a pointer to an array with data, current length of the array and a maximal length of the array - so called capacity of slice.

We can compare passing a slice with passing a pointer to it:

  1. 24 bytes and you get an address where data resides

  2. 8 bytes but then you must dereference it - go to another location in RAM to get a slice (24 bytes) and then get an address of a data. Such indirection can be more expensive operation.

Passing a slice is enough for 99% of cases. Even when you read data from file using OS methods you pass a slice as a buffer.

f, _ := os.Open("/tmp/dat")

// Create a buffer of size 5
b1 := make([]byte, 5)

// Read some bytes from the beginning of the file. 
// Allow up to 5 to be read - this value is taken from slice length 
// Read returns how many bytes actually were read.
n, _ := f.Read(b1)

More info: https://gobyexample.com/reading-files

Eugene Lisitsky
  • 12,113
  • 5
  • 38
  • 59