An introduction

Designing software is a pain. Designing software is something that can go horribly wrong, and eventually always does go horribly wrong, as changes and new requirements pile up. It is considered quite normal for a piece of software, after some long time of development, to be a huge pile of broken, disconnected, and discontinuous design. There are many, many approaches that promise you that this is the only architecture that you will ever need, and that it will provide you the best ever cleanness of code.What if I tell you that you can write code without doing design? The answer lies in a misunderstood and misused technology of old, from the times people wanted improvements desperately, but didn’t yet know improvements to what.

5th generation languages

Some of you may know, that what most of the time we use to develop software is, in fact, a third generation language. Some of you also use 4th gen langs, which are mostly domain-specific languages. And then there’s 5th gen.Interestingly, all that “progress” happened in 1970–1990, which is, a long time ago, in software world.A 5th generation language is such a language where a computer was supposed to derive an algorithm that solves the given problem by itself. That approach… failed. Computers turned out to be not very good at creating algorithms, and programmers turned out to be much better at it and very much willing to do it themselves (no wonder). And more importantly, to get computer to actually derive a good algorithm, programmers had to write it inside out, and (obviously) were very much not willing to do so.But there was some good stuff as well. First, when used properly, such languages accelerated development several orders of magnitude (i.e. expert systems). Second, they could grow without being subjected to bad initial design problems. Development was faster, because they are a flatdesign, where a developer didn’t need to think ahead. And they didn’t need good initial design, because that flat design was based on logic reasoning.Software written in 5th gen language looks like a flat set of rules (i.e. conditions), paired with the action to be performed. Things sound fairly good, till you see some “perfect” examples of how both of those are defined.

Exhibit A, “String reversal ruleset” (that is, it makes “abcde123” into “321edcba”, through some clever use of ‘$’ and ‘*’) [1]:

P1: $$ -> *
P2: *$ -> *
P3: *x -> x*
P4: * -> null & halt
P5: $xy -> y$x
P6: null -> $

Exhibit B, “I have no idea what that does and how” [1:1]:

(p Holds::Object-Ceiling
  {(goal ^status active ^type holds ^objid <O1>) <goal>}
  {(physical-object
    ^id <O1>
    ^weight light
    ^at <p>
    ^on ceiling) <object-1>}
  {(physical-object ^id ladder ^at <p> ^on floor) <object-2>}
  {(monkey ^on ladder ^holds NIL) <monkey>}
  -(physical-object ^on <O1>)
-->
  (write (crlf) Grab <O1> (crlf))
  (modify <object1> ^on NIL)
  (modify <monkey> ^holds <O1>)
  (modify <goal> ^status satisfied)
)

Exhibit C, “It is something about primes in Perl” [2]:

constraints primes/1, prime/1.

primes(1) <=> true.
primes(N) <=> N>1 | M is N-1, prime(N), primes(M).

absorb(J) @ prime(I) \ prime(J) <=> J mod I =:= 0 | true.

  1. Wikipedia: Production System ↩︎ ↩︎

  2. CHR Examples ↩︎

…Ok, enough, I think you got the idea.

Another look

Yet, it doesn’t need to be so bad. In the end of the day, does anyone actually use Perl?..

And it doesn’t need to be so limited. You don’t use JavaScript only to create rolling text, right?

And it doesn’t need to be so formal. Would prefer Swift over Haskell any day, and still get all I want from Functional programming.

Quite much, those are the only problems with 5th generation.

When I incepted Genetic system architecture, I was trying to envision a flat software design. By flat design I mean such hypothetical design, where new components, changes, additions and whatever else can be done without nesting, in one flat shared space, and with almost no modifications to existing code.

That, I figured, had to be accomplished by some automated partial superseding.

Next, it had to be split into problem domains, but in such a way, that the problems can be nested, without requiring nested solutions. The problems became data structures, domains turned to directories. And solutions, combined with the previous requirement, turned out to be 5th generation languages of old (or at least their core concepts).

Eventually, what I now call ‘MicroGene’ came out — a most basic flavor of genetic system architecture, that could be implemented on any language of choice.

The first prototype is done in Swift, doesn’t look very cool and takes too much boilerplate.

class TestGene2: Gene {
    public static let bindings: [AnyVariableBinding] = [
        /.any / !.stored1 <> \TestGene2.x,
        /.any / !.stored2 <> \TestGene2.y,
        /.any / !.stored3 <> \TestGene2.i,
    ]

    var x = Var(IntValue.self)
    var y = Var(IntValue.self)
    var i = Var(IntValue.self)

    public static let priority = Priority.normal

    public required init() { }

    public func match() -> Bool {
        return i.value.value > 0
    }

    open func execute() -> [AnyOutput] {
        let x = self.x.value.value
        let y = self.y.value.value
        let i = self.i.value.value

        let newX = x + y
        let newY = x
        let newI = i - 1
        return [
            Output(value: IntValue(value: newX), to: /.testId2 / .stored1),
            Output(value: IntValue(value: newY), to: /.testId2 / .stored2),
            Output(value: IntValue(value: newI), to: /.testId2 / .stored3),
        ]
    }
}

The second prototype is in Python (the core system is in C++), and looks much better for sure.

class Test2(Gene):
    priority = Gene.default_priority()
    
    startupHandle = Gene.var('/microgene/startup')
    
    def __bool__(self):
        if hasattr(self.startupHandle, 'test2passed'):
            return False
        else:
            return True
            
    def __call__(self):
        self.startupHandle.test2passed = None
        self.result(self.path(Test2.startupHandle), self.startupHandle)
        self.result('/test1/value', 10000)
        self.result('/test1/value', 10000)
        self.result('/test1/value', 10000)
        self.result('/test1/value', 10000)
    

class Test3(Gene):
    priority = Gene.default_priority()

    value = Gene.var('/test1/value')

    def __bool__(self):
        return self.value != 0

    def __call__(self):
        self.result(self.path(Test3.value), self.value-1)

class Test4(Gene):
    priority = Test3.priority.lower()

    value = Gene.var('/test1/value')
    
    def __call__(self):
        print(self.value)
        self.result('/microgene/shutdown', Any())

The result does look like software, not some musings of a crazy academic with infinite access to new operators that tries to sell accountants and managers on the idea of a developer-less world.

The product

In some ways, it is still a logic reasoning system, a production system, an expert system, or whatever other terms Wikipedia has for such systems.

Main difference is in the application:

  • The input problem is now an abstract state represented by some data
  • The output result is also such state
  • And the process of solving is now not of making an algorithm, but of combining software components in a meaningful way to move from input to output through solving other problems.

The system is defined as follows:

  1. There is an abstract storage space, with directories and named items.
  2. Named items are infinite lists, that contain some values, in the order they came.
  3. A Gene is a rule, that takes inputs from some paths in storage space(defined through path matching patterns), has some conditions to fire, and on firing does some operations, possibly resulting in producing new values.
  4. A value that was taken by a Gene is consumed. That is, it ceases its existence, providing uniqueness of computation operation. Consequently, no value can be accessed concurrently.
  5. There are two predefined values: ‘Startup’ and ‘Shutdown’. The first one marks the initial state of the program, and the second tells the program to discontinue execution.

Important aspect to keep in mind, is that the system is not formal. If you simply write your full normal program in the Startup Gene, then you get a usual software system. If you write everything as Genes, you get what was before called 5th generation language. What you should do, is be somewhere in-between.

The problem of developing good software design is both easy and hard. The components you use (the bits of code logic) are the same no matter what design you employ. It is only their organization that is different. The problem of organizing them (designing software) is quite tedious, and the solutions to it keep becoming ineffective in a couple if years of project’s lifetime, but they follow very simple rules. That makes software design a perfect target for being dynamically resolved by the computer itself — development without design, a problem solved by Genetic system architecture.


I’m currently working on a project that uses the Python version of MicroGene (microgene-py) to allow fast delivery cycles and easiness of changes.

As I progress, I will keep writing more specifics and benefits of using Genetic system architecture, some more interesting background, some considerations, patterns, future ideas, deeper dives into technologies used by 5th generation languages and how they tie into Genetic system architecture, and other related topics.

I will open-source microgene-py, if there’s some interest in it and as soon as I find it good enough for being released.