COLLECTION OF USEFUL IDEAS FOR MINIMAL PROGRAMMING LANGUAGE CONCEPTS Try to do more with less. Since early 2018 I've delved deeper into the world of computational models. The motivation of this endeavour is to learn about the limitations and foundations of everything that is (and not) computable. I tell myself that logically, if I learn these limitations and the foundations, it should give me a fundamental idea of how all programming languages work, and then move toward a programming language that satisfies me the most. There are problems with this though. For every new programming language to learn, there is a cost, even if they are all conceptually "the same" (I will touch on this shortly). Here are a few going from least to most costly: . There's a new syntax to learn. Typically this is dismissed as "oh but it's similar to X", but the reality is even if it's similar to X, it has its own extension to the syntax or variations. . Read about how to do certain things "the Y way". This takes time to learn and internalize. . For many PLs there is little documentation, or there is *too much* documentation. Time must be spent sorting through what's good and not. . The ecosystem size is usually significantly smaller than well established languages. So I tell myself, instead of learning new programming languages, why doesn't the world focus on learning common, useful, generic and low level techniques and structures? As I write this I stop to think: well the world does do this already...right? Then I think no: we rely heavily on available implementations for nearly everything. Vectors, Binary trees, Databases, GUI, and so on. There's definitely a subset of all programming technology that covers a large problem space. It only seems smart to utilize this fact in our every day lives, even if in some scenarios the resulting code is pen-ultimately optimal (notice I don't say "sub-optimal" - there is a weird negative connotation that is associated with the word), in favor of maintainable, portable, extensible (which can include pieces that can be replaced with ultra optimizations), and composable software. ## Representing complex data Lists and structures have a duality to them. Each have keys and values. It's how these keys and values are used that differentiate each. A list is essentially a homogenous structure. A structure is essentially a heterogenous list. If we can generalize these ideas, and optimize them later when we need, then we should do it! `[1, "2", 3.0f]` for example is equivalent to `{ 1: 1, 2: "2", 3: 3.0 }`. At a low level this could be implemented as .... pointerToListOrStructure -> (pointerToElement, pointerToElement, ...) .... and so lookups are O(n) but implementation is ultra easy at all levels of programming. ## Modules over classes for "functionality grouping" The major use-case for classes is to group and isolate behavior that belongs to an entity. Unfortunately this leads to lot of shoehorning. The second (ok, for some, its the first) major use-case is inheritance. It allows us to bring in all the functionality of an entity, plus override anything with our own desired behavior, but there are some issues with this: - If the parent class changes significantly, everything inheriting it may break. - The behavior of a parent class is hidden from its children (you can't see the code unless you look up the inherihance hiercharchy). With a module, everything exists at one level. You pull in what you need from anywhere and you know exactly where those functions and data live on the computer. Nothing is hidden. .... class Z extends Y { } // And what does Y extend? And its child? What functionality is inherited? .... vs .... import circle, square from Z; // Z might import things from Y, but it is very explicit about what it uses, rather than pull everything in, and by extension invoking all of Y's behaviors! .... ## Memory management We know that garbage collection (GC) can be an easy or hard problem, depending on needs. We also know no GC is the simplest to implement - and doesn't mean we need to manage memory manually either. ### Explict memory allocation and freeing languages In languages where you must explicitly allocate and free memory, treat your allocated memory pointer as a piece of data that must be de-allocated at the end of a function and give it a name that describes where it came from. This is to copy move or borrow semantics from the Rust programming language (and the other languages that used the same semantics before it). It results in significantly more verbose code but is also significantly safer and easier to reason about where memory is moving around. An even simpler way to express this is "functions consume data". .... PhysicsResults* ptrFromCalculateGolfBallSimulation = calculateGolfBallSimulation(); // Create a copy to use later PhysicsResults* copy1PtrFromCalculateGolfBallSimulation = copyPhysicsResults(ptrFromCalculateGolfBallSimulation); // simulateReality should free this memory, to follow move semantics. PhysicsResults* ptrFromSimulateReality = simulateReality(ptrFromCalculateGolfBallSimulation); // If we want to use the golf ball simulation data after this point, we need // to create a copy earlier. PhysicsResults* ptrFromCalibrateTransmutatron = calibrateTransmutatron(copy1PtrFromCalculateGolfBallSimulation); .... ### Implicit / garbage collected languages In a language where there is garbage collection, you should learn how the garbage collector works for the particular implementation. Most of the time, a safe bet is that the resource will be free when there are no more references to it. It is a lot easier to manage memory at the cost of performance. Just like earlier, you should eagerly stop using a resource when you can or create a new one that belongs to a different scope of the program. ### Functions as data structures You can use functions to store structural data in their argument lists: .... const food = name => color => weight => f => f(name(), color(), weight()); bananaNamed = food(() => 'banana'); bananaColored = banana(() => 'yellow'); bananaWeighted = banana(() => '9lb'); // banana is updated // A new function is passed back to continue our function calls banana = banana(function eat(name, color, weight) { return f => f(name, color, weight - 1); }); // more concise bananaWeighted = banana(bananaColored()); .... This heavily relies on partial application and first class functions.