can range over struct?

5,175 views
Skip to first unread message

linluxiang

unread,
Nov 25, 2013, 12:23:48 AM11/25/13
to golan...@googlegroups.com
I am writing a data serialisation module, and I found that "range” only supports builtin types such as string, list, and map.

This make me write a lot of repeated code like

var value interface{}
switch value.(type) {
case []interface{}:
foo := len(value.([]interface{})) // I really hate this
for index, value := range value.([]interface{}) {
// do something
}

case map[string]interface{}:
bar := len(value.(map[string]interface{})))} // I really hate this
for index, value := range value.(map[string]interface{}) {
// do other thing
}
}

I was wondering why Google implements it like this, why not just range over an interface that has Iter() function.

---------------------------------
Where Python Happens!

mail: linlu...@gmail.com
twitter: @linluxiang

Jesse McNelis

unread,
Nov 25, 2013, 12:43:35 AM11/25/13
to linluxiang, golang-nuts
On Mon, Nov 25, 2013 at 4:23 PM, linluxiang <linlu...@gmail.com> wrote:
I am writing a data serialisation module, and I found that "range” only supports builtin types such as string, list, and map.

This make me write a lot of repeated code like

        var value interface{}
        switch value.(type) {
        case []interface{}:
                foo := len(value.([]interface{})) // I really hate this
                for index, value := range value.([]interface{}) {
                        // do something
                }

        case map[string]interface{}:
                bar := len(value.(map[string]interface{})))} // I really hate this
                for index, value := range value.(map[string]interface{}) {
                        // do other thing
                }
        }

You can drop all the type asserts there, the type switch does it for you. eg. http://play.golang.org/p/d45MDc2eWY

 
I was wondering why Google implements it like this, why not just range over an interface that has Iter() function.

Iteration by calling a method requires some way of indicating when the iteration should finish, and eventually requires some kind of error handling.
If range took an Iter() interface it would then need to handle those kind of things as well as handle the concept of nested iteration.
Python does it with exceptions and a whole bunch of insanity, Go doesn't have exceptions and avoids insanity.
 

Matt Harden

unread,
Nov 26, 2013, 6:17:36 PM11/26/13
to Jesse McNelis, linluxiang, golang-nuts
If you want you can create an iterator method that returns a channel, spawning a goroutine to write into the channel, then iterate over that with range. close() the channel on the write side when done. The channel will be GC'd once there are no references to it remaining. If you only keep a writable version of the channel in your goroutine, it's possible the whole goroutine will be GC'd once there are no readable references to the channel. I'm not sure about that last part.


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Carlos Salguero

unread,
Nov 26, 2013, 6:38:23 PM11/26/13
to golan...@googlegroups.com
This is a code I'm using to iterate through a struct's fields.
Note this function is not recursive because the structs I'm using only are 1 level deep.


func structToMap(m interface{}) (retval map[string]interface{}) {

    s := reflect.ValueOf(m)
    typeOfT := s.Type()
    if typeOfT.Kind() == reflect.Ptr {
        typeOfT = typeOfT.Elem()
    }
    for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        // fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface()) // uncomment this to see what's happening
        retval[strings.ToLower(typeOfT.Field(i).Name)] = f.Interface()
    }
    return
}


Kyle Lemons

unread,
Nov 26, 2013, 6:44:00 PM11/26/13
to Matt Harden, Jesse McNelis, linluxiang, golang-nuts
On Tue, Nov 26, 2013 at 3:17 PM, Matt Harden <matt....@gmail.com> wrote:
If you want you can create an iterator method that returns a channel, spawning a goroutine to write into the channel, then iterate over that with range. close() the channel on the write side when done. The channel will be GC'd once there are no references to it remaining. If you only keep a writable version of the channel in your goroutine,

 
it's possible the whole goroutine will be GC'd once there are no readable references to the channel. I'm not sure about that last part.
It doesn't behave that way and probably won't in the future.  Imagine how confusing it would be if all of your deadlocked-on-send goroutines vanished from your stack trace!

Bryan Mills

unread,
Nov 26, 2013, 7:46:41 PM11/26/13
to golan...@googlegroups.com, Jesse McNelis, linluxiang
Um...  Yeah, don't do that - Go is not Concurrent ML.  Spawning a goroutine to feed an iterator is a good way to permanently leak memory in your Go program.

For a nice, non-leaky way to iterate over a custom data structure, see filepath.Walk in the standard library.

RickB

unread,
Dec 5, 2013, 11:41:27 AM12/5/13
to golan...@googlegroups.com
Extending the usefulness of 'range' is one of the simplest ways I can think of making Go more flexible.  I really like the things you can do with 'range', but it could do a lot more reasonably easily.

Suppose there were to be one or two standard-library interfaces that 'range' supported.  Then I could range over my custom data structures simply by implementing one of those interfaces. For example, for array-like iteration:

type ArrayRangeable interface {
    Len() int
    Get(i int) interface{}
}

Arrays and slices all provide an interface that is something like this built-in, i.e. with 'len()' and 'array[index]' indexing.

Or, for iterable iteration is the slightly-more complex interface:

type UnboundedRangeable interface {
    HasNext() bool
    Next() interface{}
}

As with iterators in other languages, this would only be traversable once, which raises issues that would need to be thought through, so 'UnboundedRangeable' might need further work. But the basic idea is simple in principle.

One disadvantage of the suggestion is that neither of these interfaces has strongly-typed values at compile time because they both rely on 'interface{}'.

minux

unread,
Dec 5, 2013, 7:59:42 PM12/5/13
to RickB, golang-nuts

in fact, that is the biggest problem. losing type safety in range is not the way to go.

this issue has been discussed at length on this mailing list, please search the archive for prior threads.

Reply all
Reply to author
Forward
0 new messages