Vlad-Shcherbina / icfpc2019-tbd

0 stars 0 forks source link

Internal interface design recommendation: "flat, imperative code". #1

Open fj128 opened 5 years ago

fj128 commented 5 years ago

I want to quickly reiterate the principle of "flat, imperative code" for designing internal interfaces that allows several sleep-deprived people to use each other's code productively, especially in the context of the ICFPContest. I originally formulated this after the 2009 or so contest, and it has served me well with my professional programming as well.

You should strive to have three types of entities in the program:

  1. Passive data: arrays, dictionaries, dataclasses (previously namedtuples), with simple utility accessor methods when that's more convenient than using standalone functions/helpers.

  2. Functions and classes that do things with passive data but don't have a will of their own. An AI should take a World and return an Action, a World might have a method apply_action.

  3. A single (per program invocation) piece of code that actually has a will and decides what to do. That's typically five to twenty lines of code routing passive data between functions and classes that do things. We usually have several of them in the codebase, each participant has their own that they use for tinkering, plus the main submission, plus visualizers and interactive solvers.

Motivating anti-example: when the task consists of implementing a Pacman-like game, it's tempting to design a Game class that is parametrized with AIs for the pacman and the ghosts and contains the main loop that tells those AIs to do stuff. Furthermore, it's tempting to parametrize those AIs with the Game class and have them tell it to do stuff, instead of producing passive data Actions.

But then: how do you implement a graphical visualizer that has its own main loop? What if you want to let a human control the pacman, do you use a fake pacman AI that actually communicates with the GUI thread? How do you store replays? How do you replay replays? How do you verify that different implementations of game logic or AI produce the same result? How do you verify against an external implementation of game logic?

If there's a single place where things are actually glued together, and if they are glued strictly through passive data (and not any method calls), and that place can be copypasted and adjusted as needed, then all the above very real problems become trivial.

Note: "imperative" is not opposed to "functional" here, I don't care if we do world.apply(action) or world = world.apply(action), I actually prefer the functional style unless the performance will obviously suck. It's opposed to functions parametrized with functions and objects parametrized with objects.

Note: it's OK to have a convenience function that glues a bunch of stuff that goes together 90% of the time, provided that all of that stuff is public and it is trivial to copypaste and customize that function in the remaining 10% of the cases.

serokellcao commented 4 years ago

Just a small nag, you should have probably put this disclaimer in the front, for people to understand you right away

Note: "imperative" is not opposed to "functional" here, I don't care if we do world.apply(action) or world = world.apply(action), I actually prefer the functional style unless the performance will obviously suck. It's opposed to functions parametrized with functions and objects parametrized with objects.

I think that "flat" describes it well enough, and I would argue that this approach is compatible with my suggestion of using declarative code on the top-level, and having semantic representation of values (as either a function, inlinable by the compiler, or as a variable or constant with a semantic identifier) on the low-level.