google / shenzhen-go

Experimental visual Go environment
Apache License 2.0
464 stars 44 forks source link

Channels should be more like cattle, less like pets #14

Closed DrJosh9000 closed 6 years ago

DrJosh9000 commented 7 years ago

🚨 Self-directed rant. 🚨

I think the model of channels (and how they're used) has to change before progress can be made on what I think is a major feature - visually observing runtime behaviour.

The current model of creating every channel and giving it a name makes sense if you understand the code coming out the other end, but I can't think of another visual system that does it this way. Referring to the inspiration for this project, SHENZHEN I/O, it has circuit traces (channels) which are anonymous, and connect parts which identify communication via the pin it is communicating on.

I originally thought this low-level approach leads to confusion (pin x0 on one part goes to x3 on another ...) so pressed on with letting the user give each channel a name and having it magically detect channel usage (by parsing the user's Go, go/parser, go/types). It's clever, but not clever enough. SZ-GO would have to get into the business of statically analysing and rewriting other people's code much more precisely in order to progress.

To explain: This is important for what I'm calling "smart channels" - visualising what is passing through a channel at runtime. To do that the channel has to be swapped at build time for some kind of weightier structure that intercepts channel communication, e.g.:

type smartChannel struct {
    in, out chan interface{}   // Could generate these smart channel types with a template, to handle specific value types.
        // Other fields for tracking statistics.
}

// Run in a separate goroutine:
func (c *smartChannel) Track() {
    for x := range c.in {
        // Accumulate information to display here.
        c.out <- x
    }
    close(c.out)
}

but for this to work, analysis is required for knowing whether to swap each usage of a channel identifier with smartChannel.in or smartChannel.out. It's simple if it is all one of <-, close, or range - the current code works great for those cases. But it doesn't handle channels passed through function arguments or fields. There's also this potential case:

// Third-party package:
func SomeoneElsesCode(info chan interface{}) {
     x := <-info
     // do something
     info <- y
}

// An SZ-GO program:

var info = make(chan interface{}, 0)

// This channel gets exchanged at build-time for:
var info = &smartChannel{ ... }
// in order to visualise information flowing through "info".

go func () {
    // The user wrote in some goroutine:
    ...
    SomeoneElsesCode(info)
    ...
}()

It can't substitute SomeoneElsesCode(info) for both SomeoneElsesCode(info.in) and SomeoneElsesCode(info.out) simultaneously - that's impossible. It could handle it by reparsing and rewriting SomeoneElsesCode but that seems to be too unreasonable - to go that far probably needs compiler changes (or something on the order of compiler changes). And if the compiler is being changed, why not just change the standard channel implementation to implement smart channels? Either way feels like dangerous territory for a spare-time project :-)

It is comparatively much easier to simply lock down whatever channels are being used, and how they are used:

  1. Declaring the "real" channels hidden inside the main/Run function rather than as package variables.
  2. Every goroutine, which today is pasted into an anonymous goroutine function in main/Run, becomes a standalone function with read- or write-channel arguments, e.g.
func myExampleGoroutine(in <-chan int, out chan<- string) { ... }
  1. Goroutine invocation in main/Run passes in the channels and calls the function.

The upside to this is most of the existing channel extraction and renaming business just goes away. Various UI bits could be easier to implement too.

The downside with this proposal is that the user has to declare channel usage at both ends. If everything is a Code part, that's as many as 2 channel declarations (as arguments) per channel (value), i.e. twice the work for the user compared to the current scheme.

joeblew99 commented 7 years ago

@DrJosh9000 i just tried the cattle branch. Its coming along well :) I used a few of these sort of tools as an architect years ago before really getting into coding.. Stuff like this: http://www.generativedesigncomputing.net/search/label/Grasshopper

I think i see what your on about in this rant :) It boils down to interface versus implementation. go-kit and go-micro provide a Inversion of Control using good old interfaces. So cant you follow the same principle ?

For example: https://github.com/tideland/gocells

DrJosh9000 commented 7 years ago

@joeblew99 Thanks for trying it out, and the interesting links! Definitely food for thought.

DrJosh9000 commented 6 years ago

Closing this in favour of #17 - a proper design doc should cover this.