On further reflection, I like this generic-map structure so much that I'm going to use it for everything. Every message that is sent anywhere in the system will be of that form. In fact, I will call the basic data structure a "Message".
type Message map[string]interface{}
And all the different specific data types will just be defined as specializations of that type:
type Abstraction Message
type Work_Description Message
The differences between the various specialized messages will be that they have different 'mandatory' fields, which will be filled in by their creation functions:
return Abstraction { "type" : "abstraction",
"id" : 1,
"version" : 1,
}
}
Why use such a generic structure for everything? Because I want this system to be able to run forever, with different Abstractor types getting added to the system, with old Abstractor types getting upgraded -- all without ever needing to shut down to recompile all the code.
If you used 'normal' data structures, you would have to shut down and recompile every time a new data type were added to the system.
You might not even be able to get to all the code! At first every Abstractor in this system will be a goroutine and they will all be running in a single process. That would probably work well enough to use most of the computing resources of one large box. But later it would be easy to add a portal so that Abstractors running in a separate process, or even on a separate box could become part of the system, and then you would be running a single system across the internet.
The system gradually evolves, either because human programmers are adding new Abstractor types and new Abstraction types, or because we find some way to add those automatically. And there is never any need to stop anything while the system evolves. The new Abstractors just show up and start talking to the Bulletin Board, and everything else keeps working.
You could even add new fields to a given Abstraction type without damaging old Abstractors that use that type. They simply don't use the new fields, but can keep using the old fields the way they always have done.
Imagine this:
You have a bunch of vision systems that are bulldozers -- or something -- collecting regolith from the surface of an asteroid. (Do asteroids have regolith, like the Moon?) And they keep getting into some kind of trouble in certain situations. They are having vision system failures once in a while that require expensive -- and slow -- human intervention.
So the vision engineers spring into action. They take a look at the stored image sequences from when the bulldozers got into trouble and they say "Oh, I see what's happening. Wow, we never thought of that!" And they write some new Abstractors that will detect just that special trouble-making situation and respond to it.
The new code gets transmitted to the bulldozers. The code gets launched as a separate process that knows how to use inter-process communication to become part of the running system. No need to shut anything down.
The upgraded bulldozers quit having that particular problem.
Or maybe, somehow, those new Abstractors get invented automatically.
Either way, we have a vision system that can change or add to the code it's using, adapting to new or unanticipated challenges, without ever shutting down.
Just like you!
Here's a complete little example program, showing Messages getting specialized into Abstractions and Work_Descriptions:
// start of code
package main
import (
"os"
"fmt"
)
var fp=fmt.Fprintf
type Message map[string]interface{}
type Abstraction Message
type Work_Description Message
func New_Abstraction() (Abstraction) {
return Abstraction { "type" : "abstraction",
"id" : 1,
"version" : 1,
}
}
func New_Work_Description() (Work_Description) {
return Work_Description { "type" : "work_description",
"version" : 1,
}
}
func Print_Abstraction ( a Abstraction ) {
fp ( os.Stdout, "Abstraction: |%#v|\n", a )
}
func Print_Work_Description ( wd Work_Description ) {
fp ( os.Stdout, "Work_Description: |%#v|\n", wd )
}
func main () {
a := New_Abstraction()
Print_Abstraction ( a )
wd := New_Work_Description()
Print_Work_Description ( wd )
}
// end of code
And the output :
Abstraction: |main.Abstraction{"id":1, "type":"abstraction", "version":1}|
Work_Description: |main.Work_Description{"type":"work_description", "version":1}|
But if you try to pass an Abstraction into the Print_Work_Description() function, you get:
cannot use a (type Abstraction) as type Work_Description in argument to Print_Work_Description
No comments:
Post a Comment