OK, I've barely got twenty lines of code into this grand project so far, and it's already time to re-architect!
What I showed last time was the same architecture I have been using since Dinosaurs Roamed the Earth. It works like this:
- You define a data structure that represents the problem space.
- You write a New function for the data structure.
- You write a bunch of functions that operate upon that data structure.
- Each function takes a pointer to the data structure as its first argument.
- The main program creates the structure, owns it, and operates upon it.
- You keep getting older until someday you don't.
When I re-re-(re-?)started Tachyon recently, this was the style I automatically used, as a dog returneth to its vomit. Now, before we go too far in this well-trod path I want to recall that I have recently learned a new style and a better, thanks to the fabulous mechanisms for threading and communication of the fabulous new language C-2012, sometimes also called "Go"! Let us take a road less traveled by, for that will make all the difference. Because you've been down there, Neo. You already know that road. You know exactly where it ends. And I know that's not where you want to be. We have come to a fork in the road. Let us take it.
The new style that Go has taught me is this:
- Instead of a data structure, we have a running goroutine.
- Instead of fields in a data structure, we have local variables in the running goroutine.
- There is exactly one function call. It starts the da- Look at that. I started typing "data structure". Wake up, Neo. The single function starts the goroutine running, and returns the channel that it is listening to.
- All further requests that you can make of the goroutine are made through that channel.
- Sometimes you can pass in your own channel as part of a request. That is your reply-to channel, where the goroutine will send its response to your request.
Here's what it looks like in code:
(from checkin e39407fd55986f916308a86ebcf11333039cc793 )
Here's the only public function:
func Start_Bulletin_Board ( ) ( Message_Channel ) {
requests := make ( Message_Channel, 5 )
go run_bulletin_board ( requests )
return requests
}
It creates the requests channel, passes that to the goroutine, and passes the channel back to the caller.
Here's the run function:
func run_bulletin_board ( requests Message_Channel ) {
bb := Bulletin_Board { output_channels : make ( map[string] []Message_Channel, 0 ) }
for {
msg := <- requests
fp ( os.Stdout, "MDEBUG BB |%#v| gets message: |%v|\n", bb, msg )
}
}
And here it is being used in main() :
bb_channel := t.Start_Bulletin_Board ( )
for {
bb_channel <- msg
time.Sleep ( 2 * time.Second )
}
No race conditions in accessing the fields of a structure, in spite of arbitrary extensibility. This goroutine can be used by any number of other routines.
This is a beautiful style and it's going to change everything.
Cool! Thanks!
ReplyDelete