Author Topic: Simon blogs  (Read 85306 times)

0 Members and 1 Guest are viewing this topic.

Offline Simon

  • Administrator
  • Posts: 3284
    • View Profile
    • Lix
Re: Simon blogs
« Reply #315 on: March 19, 2022, 10:44:22 PM »
Unix Wizard (click to show/hide)

This is a poster from 1985. What do we learn from it?
  • You can do everything in the shell.
  • You have many separate ingredients to mix and match.
  • Interesting data structures arise from such mixing.
  • Unneeded leftovers, pipe them into null.
  • The shell script is not an ingredient; it's a suggestion for how to combine ingredients.
  • By that time, C had superseded B (the programming language) since the B pot has a crack.
  • Cats follow those proficient in the mystic arts.
The strangest thing in the picture is the pot of oregano; there is no classic Unix tool called oregano. This oregano is presumably a joke.

The same picture as a 32 MB .png.

-- Simon

Offline Simon

  • Administrator
  • Posts: 3284
    • View Profile
    • Lix
Re: Simon blogs
« Reply #316 on: June 09, 2022, 02:40:26 AM »
Mother of all Principles

Many software development principles, e.g., many of the object-oriented guidelines, follow from DRY.

DRY (Don't Repeat Yourself): Every piece of knowledge should have a single unabiguous authoritative representation in the system. Antonym: WET, Write Everything Twice, We Enjoy Typing.

  • Repetition in logic calls for abstraction.
  • Repetition in process calls for automation.
  • Extract duplicated logic into a new function/class/template/..., and call it from the original places.
  • Extract interface from several similar classes, and call the original functionality through the now-common interface.
  • Interface Segregation Principle. Have concise interfaces so that usercode can apply it where it fits, then usercode doesn't have to reinvent the wheel -- now you've dried usercode that you didn't even write. Also, test code doesn't need to mock too many unneeded methods.
  • Group closely related fields into a structure. It starts with struct Point { int x; int y; } or struct Version { int major; int minor; int patch; }. At minimum, the DRY happens when passing, re-assigning, ..., several fields when functions forward the arguments to other functions.
  • Write automated tests.
  • Write scripts or use test/build/packaging servers for common tasks.
  • Document solutions whenever people ask the same question more than once. I.e., have FAQs to avoid answering the same question every time.

About FAQs. Continuously convert FAQs into proper documentation, or your FAQ page will become the only important documentation, which is bad for the bigger information architecture.

A grotesque disease in modern documentation culture is to design and write FAQ sections upfront, before the release of the product, with questions that the authors would like to be asked, not with questions that really came up during support. You can often detect these anticipated questions from self-indulgent wording in the question.

Bad! FAQ are frequently asked questions, not frequently anticipated questions. Anticipated questions go into the normal documentation. If you're so terrible that all your documentation is bullet-point style without bigger structure, you don't solve the problem by calling it FAQ, you solve the problem hiring better technical documentors, better tech support, and flattening your processes to connect the two teams.

Documentation can violate DRY if you can't easily fix it. E.g., if people don't read the manual and only look in some other place, sure, put the answer in two places. But let this insight guide you to refactor the documentation into where people really look.

So much for DRY, the mother of most principles.

But some principles don't taste like DRY, no matter how long we chew. Why is a long, deeply-nested method bad? It doesn't necessarily repeat any logic, and it automates everything. It can be 100 % DRY and it still doesn't feel nice to edit/understand.

I believe that nearly all principles follow from either DRY or from SLAP.

SLAP (Single Layer of Abstraction Principle): Each piece of code should be at a single level of abstraction. Don't mix the high-level structure with the low-level details.

  • Refactor the long function into several shorter functions, then either call them from the original one after another, or add even more structure that became apparent during the split.
  • Don't nest too deeply. Don't mix complex preparation/iteration in a single function with the payload work per element. Describe the work per element in one method and the iteration in another. Bonus: The work per element, as a function, becomes a nice argument to higher-order functions (filter, map, fold).
  • Single Responsibility Principle. Make a class do one thing well. Split big classes with multiple responsibilities into several classes. The original class can still stay as a (now much shorter) mediator to tie the several classes together for the original functionality.
  • Classes capture responsibilites, not necessarily real-world objects. Reason: Real-world objects can have too many responsibilities.
  • Sometimes, real-world objects appear to change their own type, a behavior that you can't capture in classical OO when classes correspond to real-world objects. Solutions like the strategy pattern, the state pattern, e.g., Lix jobs, ..., all have the same fundamental idea: A small class with the fixed high-level structure calls into other small classes with the payload work or the dynamic replaceable behavior. A very nice nontrivial example of SLAP.
  • Ask your caller for a favor if it helps you design a cleaner interface. Don't do his dirty work and the task; do only the task. Accept non-null of the correct type. Let the caller type-convert, let the caller skip calling you if it's null.
  • Null Object Pattern! Let the polymorphism handle the no-op case, and call stuff unconditionally. Easier code both on the caller and on the callee!
I merely don't see how the Liskov Substitution Principle fits into either DRY or SLAP. Maybe not breaking users' expectations is a third fundamental idea that neither DRY nor SLAP covers?

-- Simon
« Last Edit: June 09, 2022, 03:48:06 AM by Simon »

Offline Simon

  • Administrator
  • Posts: 3284
    • View Profile
    • Lix
Re: Simon blogs
« Reply #317 on: June 11, 2022, 11:37:16 PM »
Guinea Pig Diet #4

I weigh 89 kg and I'm trying to lose weight again.

I resisted the urge to order pizza. Instead, had an entire cucumber and two bananas. There are still bell peppers and apples around. Nonetheless, very hard to resist the pizza.

The new and exciting idea is to replace snacking (outside proper meals) with entire cucumbers. Instead of putting the little cucumber slices on bread, I'll have the entire cucumber as a raw snack, gnawing away over time like a true guinea pig. Only 40 kcal a pop.

I don't know if I want another bet with LF hosting costs on the line. Last year, it didn't spur me enough to hit 80 kg. I even feel that everybody else will be discouraged from donating if I do it. :lix-ashamed: They'll think: Why donate 5 dollars if Simon will donate 60 anyway, what good does a small extra donation do at all... In hindsight, maybe it was even counterproductive to publicize my 2021 weight bet in this way.

I'll find a nice way to get my bikini body. :lix-cool: Does somebody want to join the weight loss? Who's up to pursue a goal together until end of 2022?

My favorite animal is still the African crested porcupine, I like giant anteaters too, and I've recently grown to like the manul, a.k.a. Pallas's cat. International Pallas's Cat Day is April 23, so it'll be a while until I write an entire topic on manuls like my 2020 topic on the giant anteater.

But in the Lix release thread, I like to post an animal picture with each release, ideally a picture that fits the feeling of the release. Expect some manuls for the next couple releases!

-- Simon
« Last Edit: June 12, 2022, 10:06:24 AM by Simon »

Offline Simon

  • Administrator
  • Posts: 3284
    • View Profile
    • Lix
Re: Simon Blogs
« Reply #318 on: June 26, 2022, 08:46:03 AM »
Simon Rhymes

filter, map, fold,
das Glück ist dir hold.
Doch std::transform,
das wurmt dich enorm.

I wrote this cheesy poem in 2016. Detailed explanation follows -- not all readers are proficient both in software development and in German here. A typical piece of code that appears every single time in an introduction to functional pipelines is:

const int y = [1, 2, 3, 4, 5]
    .filter!(x => x > 2)
    .map!(x => x*x)

This sets y to 50 because 50 is 3×3 + 4×4 + 5×5.

Now for the explanation of the poem. {

Functional pipelines for data are nice: Each step relies only on the output of the function from the previous step, not on any properties/mutation/earlier side effects to/of a publicly visible buffer containing the original input. Fewer bugs because we avoided mutable data.

Even though you can chain higher-order functions such as filter, map, fold in any sensible order, e.g., map first, then filter some, then map some more, then filter some more, ..., the most common order is exactly the order of the poem:
  • filter: First discard some elements that we want to ignore.
  • map: Transform the remaining elements.
  • fold: Collect the mapped elements into a final result.
These functional pipelines are notoriously hard to express in C++ because you can't write them as pipes even with the C++17 standard library. You don't pass the collection itself as a single argument, instead you pass two arguments, a start iterator and an end interator. Therefore, you can't chain the calls à la source.f().g().h(); but have to put every call into a separate statement, introducing a new name for every intermediate result.

In particular std::transform is the equivalent of map in that world, and the annoyance (having to put every step of the functional chain into a separate statement) feels already big enough here that it's nicer to write a conventional loop over the collection instead of calling std::transform.

You're annoyed enough in C++17 that you might consider the range-v3 library for C++17 or to move your project to C++20 where these pipeline-friendly ranges are part of the standard library.


Back from C++ to D. When I write functional pipelines in D, I rarely call an explicit fold() in the last step. It's much more common to call one of these:
  • join() some mapped strings into a single long string, possibly join(" ") or join(", ").
  • array() to keep the results in an eagerly-allocated random-access array, usually because we'll only store the result for now.
  • sum() on a list of numbers.
  • any() or all() on a list of bools.
All of these are (semantically, not implementation-wise) special cases of fold().
For example, list.sum() is
list.fold!((a, b) => a + b)(0).
The better name goes a long way though to make the pipeline more readable, especially if the more specialized function comes directly from the standard library.

Even map(list, func) and filter(list, predicate) are special cases of fold(), as explained in the blog article The Universal Properties of Map, Fold, and Filter with the maximum-possible amount of category theory. The gist is: filter() accumulates (folds) the list into a new list, possibly doing nothing instead of appending the next element. Similar for map().

-- Simon
« Last Edit: June 26, 2022, 10:11:36 AM by Simon »

Offline Simon

  • Administrator
  • Posts: 3284
    • View Profile
    • Lix
Re: Simon blogs
« Reply #319 on: June 27, 2022, 06:38:05 PM »

A job very hard.

English and German grammar suck. German sucks more than English because in German subordinate clauses, you the verb in last place, where the reader the verb won't find before he already the meaning of the sentence has forgotten, put must. But English isn't so much better in other regards, either.

Topic of the sentence, I want to put it in first place and not risk the sentence looking odd.

Noun proper should always come before their adjectives explaining, like in French, a langue très bèttre in regard this. Sometimes, I've written adjectives and forgot to add the noun.

Lots of parentheses, I want to add them everywhere. And I want to write in a two-dimensional way, e.g., I want to write a sentence and then add bubbles (with more information) around the sentence, tied to specific (parts in the sentence). The bubbles contain elaborations/definitions/... and you should be able to show (or hide) them (on demand).

C++ uniform initialization syntax, maybe it should become standard in English, too. I have Car{nice, black, Diesel} and I drive{with Car from earlier in this sentence, to Friend, Reason{visit, Reason{he is nice, I haven't seen the friend for a while}}} this afternoon. And now the braced initializer list for drive became too long, I want to refactor it; only the friend should be visible by default, and everything else from the list goes in a bubble{hidden by default, tied to drive}.

-- Simon
« Last Edit: June 27, 2022, 06:45:09 PM by Simon »

Offline Simon

  • Administrator
  • Posts: 3284
    • View Profile
    • Lix
Re: Simon blogs
« Reply #320 on: June 28, 2022, 10:56:23 PM »
Apropos uniform initialization syntax. :8():

I've used C++ for 16 years now and I still don't know every case of where the language guarantees a zero initialization and when it may leave something uninitialized.

Everybody learns early on that this int is uninitialized:

void foo() {
    int a1;

And that's about as far as "everybody knows" goes, I'd wager. :lix-evil: Only many people, including me, know that these two are zero-initialized (although I'd still add the explicit = 0):

int a2; // at global scope
void bar() {
    static int a3;

The design reason behind this is that these ints live in "static land" instead of going on the stack where the zero initialization would cost runtime: The BSS section is a memory region that comes preinitialized for free when the operating system loads your executable and has to copy all of the executable code into memory anyway.

But I have no clue of the following, although I see this occasionaly in the day job, usually in C code that somebody renamed to .cpp later:

void baz() {
    int arr1[5] = {};
// Is anything zero?
    int arr2[5] = {0}; // Are the subsequent four ints also zero?

And do those initializations mean something different in C and in C++? Does it depend on the version of the C standard?

Here are some fun ways to initialize a single int, most of which only arise in theory. This is perfectly legal code that compiles. Are they all zero, are only some zero, are they all possibly uninitialized? I don't know.

void blub() {
    int a4{};
    int a5 = {};
    int a6 = int{};
    int a7 = int();
    int a8{int()};
    int a9 = {int()};
    int* p1 = new int;
    int* p2 = new int{};
    int* p3 = new int();

The two cases I should look up for definitive clarification are a4{}; and a5 = {};. The others might also be enlightening eventually, but expressions such as int() come up rarely enough that you can deem those esoteric.

And it doesn't end with ints where you have a chance at initializing them explicitly with zero. Occasionally useful in real life: You have std::vector<int> and you want to enlarge it by calling resize(). Does it zero-initialize the new values?

I have dark memories of possible nonequivalence of the following. It might be a false memory from early learner days in 2006, but I've never clarified it. Assume X is a class with a custom default constructor, and no other constructors (in particular, X doesn't define a constructor that takes a std::initializer_list). Do all of these run the default constructor? Quick reality check with g++ --std=c++17 tells me: Yes, all of them run the default constructor. But does the language really guarantee it, ever since C++98? I assume so, then...

void blip() {
    X x1;
    X x2{};
    X x3 = {};
    X x4 = X();
    X x5 = X{};
    X* x6 = new X;
    X* x7 = new X();

I should really read the standard directly more.

-- Simon
« Last Edit: June 29, 2022, 12:05:26 AM by Simon »

Offline Simon

  • Administrator
  • Posts: 3284
    • View Profile
    • Lix
Re: Simon blogs
« Reply #321 on: June 29, 2022, 11:38:43 PM »

Summer is too warm. When will be the next wonderful thunderstorm?

Upside of the heat: Worse mood yields more Simon rants. The problem is finding a peaceful moment to write it down here; I'm striving for a certain minimum quality. Careless prose isn't fun to read.

One of my strategies to counter the heat is to open the windows wide at night, to let the cool air come in -- well, the relatively cool air. At night, it's still warm enough to walk outside in shorts and T-shirt. Plainly, it doesn't get properly cold these weeks. I have flyscreen in practically every window. The flyscreen is worth as much as all the gold in Fort Knox, i.e., as much as performant backwards framestepping. I don't know what people without flyscreen do.

Then again, people also play framestep-less Lemmings without raging. I guess you can treat the experience as a kind of meditation, hmm, even I like to play L1 in Dosbox occasionally for ye olde AdLib nostalgia.

Loap and L2Player, these projects must grow. Custom L2 packs are nice alrelady in DOS L2, but Kieran and Ste had to hold back on puzzle difficulty lest they introduced accidental execution difficulty. I deem level design more an art than a science. Let's not curb the designers' creativity, let's offer them the best tooling we can.

-- Simon