Technologies I’m Impressed by

Following is the list of technologies I’m Impressed by, a completely subjective metric. Since I’m not impressed often, these deserve to be listed.

Impressed

In arbitrary order:

  1. JetBrains IDEs. The thorough understanding of the code is top notch.
  2. Vim. Amazing productivity when editing text.
  3. Linux. No explanation required πŸ™‚
  4. FHIR. Very well thought through data exchange standard for healthcare. Extensible everywhere. Profiles to mandate constraints.
  5. AWS CDK. Amazing productivity gain over CloudFormation due to semantic involvement of the tool. It’s about expressing what you want to achieve, not assembling the solution from what you have.

Hope you have found something new to you in this list. In this case, I recommend taking a look at it.

Type Casting in TypeScript

( During code review, looking at TypeScript code. )

Colleague: … type casting …

Me: hold a sec, I don’t remember about anything called type casting in TypeScript

Colleague: you know, “as”

Me: yep, there is “as”, I just don’t think it was called type casting when I last re-read the official documentation a couple of months ago.

Colleague: hold a sec, here (shows me Google search results)

Search Results

My suspicion that something is off just grew when no result came from the official documentation.

TypeScript’s “as”

Clicked on few of this articles. They are talking about “as”. What does official TypeScript documentation tell us about “as”? It’s found under the heading Type Assertions. I didn’t see anything about type casting in the documentation.

Type Assertion vs Type Casting

Same thing, you are just showing off by being pedantic about naming!

Statistically probable response

What if I told you there is a practical difference between type assertion and type casting which confused my colleague?

Sometimes you will have information about the type of a value that TypeScript can’t know about… Like a type annotation, type assertions are removed by the compiler and won’t affect the runtime behavior of your code.

Official TypeScript documentation

You are letting TS know which type the value is. This information is used at compile time. Nothing happens at runtime. “cast” to the wrong object and no exception is thrown. That’s type assertion in TS.

In other languages casting has runtime implications.

Reality Check 1

Googled “type assertion vs casting typescript”. First result is What are the difference between these type assertion or casting methods in TypeScript (Stack Overflow). Oops. People are using “casting” all over, even on Stack Overflow… except that one comment at the end of the page:

(myStr as string) isn’t a cast, it’s a type assertion. So you are telling the compiler that even though it doesn’t know for sure that myStr is a string you know better and it should believe you. It doesn’t actually affect the object itself.

Duncan, Apr 13, 2018 at 14:26

… which is countered by

Coming from C# and since Typescript has many C# features, I’d say it is

user2347763, Apr 13, 2018 at 15:17

I’ll have to disagree on the grounds that cast expression in C# “At run time, … might throw an exception.”

Reality Check 2

… whether the underlying data representation is converted from one representation into another, or a given representation is merely reinterpreted as the representation of another data type …

https://en.wikipedia.org/wiki/Type_conversion

So, apparently, type assertion is a specific case of type casting.

Conclusion

I was wrong thinking that “type assertion” is not “type casting”. Apparently, it is. It’s just a specific case. The phrase “In other languages casting has runtime implications.” (that I wrote above) is apparently not correct in the general sense despite Java and C# popping up immediately.

What I’m still having hard time to understand is why those articles choose to use the generic wording. I think we should always strive for precise wording.

If you want to be part of search results, fine, I get it. What’s preventing you from “In TypeScript, type casting is implemented using type assertions (a special case of type casting) that only have compile time implications” (or something similar) though?

Takeaways

  • Always check official documentation (apparently it needs to be said for the 1000th time)
  • If you are posting an article, I recommend sticking to the wording in official documentation, especially when it’s more precise.

Can we make this article more precise? Let me know πŸ™‚

Empty Arrays for Cleaner Code

Background

Let’s look at the following piece of code (in an abstract programming language) :

if chairs {
  for chair in chairs {
    clean(chair)
  }
}

The Problem

It’s more code that we would like to have. What’s holding us back? chairs can be null, which is not an array (aka list), therefore requiring the wrapping if. Without that if, for can fail with an error because it won’t be able to iterate over chairs (when it’s null).

Side note: any time a variable can be of different types (in our case chairs can be an array or null), there will be some code to deal with this situation. Try to avoid these situations.

Solution

Decide on a code convention that says that absence of items is represented by an empty array, not null. Your code will become more uniform and you will get rid of the if. Then the code above becomes:

for chair in chairs {
  clean(chair)
}

Please note that this solution is a tradeoff. If memory usage is very important in the application, this might not be a good solution.


Hope this helps. Have a nice day!

Update 2023-07-13

Real code example:

const tags = describeImagesCommandOutput.Images[0].Tags ?? [];
console.log('tags', tags);
const newTags = tags.filter(tag => tag.Key && TAGS_TO_COPY.has(tag.Key));
console.log('newTags', newTags);

My advice above would eliminate the ?? [] part.

Naming in Software – Practical Guide

The title of the post is the title of the book that I wanted to publish for quite some time now. While I was thinking about phrasing and gathering content, somebody else beat me to it with Naming Things: The Hardest Problem in Software Engineering. The main issue that I wanted to solve is now solved. Programmers don’t have an excuse for poor naming anymore.

In light of this event, I’ve decided to make small complementary post out of the materials that I have gathered and move on, focusing on Next Generation Shell.

Me and Naming

I have over 20 years of professional experience in programming. During that time, as many others, I’ve also noted the struggle when it comes to naming.

Here is a list of my accepted naming contributions to various projects.

  1. iterators – function shoes_in_my_size naming 2020-02-16, “The Rust Programming Language” book
  2. Constructors – Get_Contents() method is misnamed 2020-02-23, MS C++ Documentation
  3. Rename howMany() to countSelected() 2023-01, MDN
  4. nilJson naming issue in readme 2023-04, Otterize

Naming Things, the Book

I skimmed Tom’s book to understand how similar it was to what I was about to write. Quite close. If you are struggling with naming, go and read it.

There is some amount of fluff which I think my book would have less. Example: convincing people that naming is important while they are already reading the book.

Overall, I do recommend the book though.

Especially I recommend this book to AWS as an organization, I guess along with other books about code quality in general. AWS, your de-prioritization of code quality is staggering. I mean observable output here, not the stated “Insist on the Highest Standards”.

6.2.6 Use accurate parts of speech

Adding negative example from AWS:

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html

SUCCESS and FAILED are not the same part of speech.

7.2.3 Omit Metadata

Additional reason to exclude data type from the name is to avoid additional changes anywhere in the code except for the declaration.

elt in items

Is items a list here or a set? Probably not important, the code should work with either. On the other hand, changing data type from list to set in the following example will make the code incorrect:

elt in items_list

8.2.4 Use similar names for similar concepts

Adding negative example from AWS, which time after time fails to give consistent names across their APIs.

How do you limit number of results from an API? MaxResults, maxResults, MaxRecords, MaxItems, Limit, limit, … Details at AWS API pagination naming.

It looks like consistent naming is valued less than independence of teams and ability of teams to perform uncoordinated work.

8.2.5 Use consistent antonyms

Adding example.

When I’ve got to name Option type (represents a container that can hold a value or can be empty) in Next Generation Shell, I went with straightforward antonyms.

  • Box (super type)
  • EmptyBox
  • FullBox

Authors of other languages preferred other naming conventions:

Scala: Option, None, Some

Haskell: Maybe, Nothing, Just

Perspective

Information Loss

In my perspective, giving inadequate names is part of a larger issue – Information Loss. Each time you give a name, think which information is now in your head which will be helpful to the reader of the code. If you don’t phrase it concisely and precisely, information loss occurs between your head and the code you are working on. There are several common types of errors one can make:

  1. Don’t provide enough information. Causes the reader to investigate in order to recover the information.
  2. Provide wrong information. That’s the worst, it’s misleading the reader.
  3. Provide too much information. The reader then must sift through the information to get to the relevant parts.

API

Sometimes it’s useful to think of methods as an API. That’s why method names shouldn’t include implementation details (with rare exceptions when they are important to the caller). Think of methods’ names and parameters’ names as a short version of API specification.

Identifiers

Tom’s book deals with naming identifiers, such as functions, classes, variables, etc. One step before naming an identifier is the question whether there should be an identifier.

Avoid Naming

Sometimes, the additional cognitive load is not worth it.

# Bad
chairs = fetch_chairs()
sorted_chairs = chairs.sort()
# also, now have to use the longer identifier in the code below

# Good
chairs = fetch_chairs().sort()

Apply your judgement of course. If it’s a 20 step process, additional identifiers in the middle do contribute to understanding. You still probably don’t want an identifier for each and every step of the calculation.

Do Name – Magic Numbers

Avoid magic numbers through naming. Please ignore whether result is a good name πŸ™‚

# Bad
if result == 126 { ... }

# Good
NOT_EXECUTABLE = 126; # Or better, part of an Enum

if result == NOT_EXECUTABLE { ... }

Do Name – Repetitive Code

If you notice code that repeats, with rare exceptions, you should refactor your code extracting that code to a function or a method with a name.

Several Identifiers

Sometimes a function, a method, or a class do several things. In this case, you might struggle to name it. In a perfect world, the solution to this is refactoring to appropriate pieces.

Test Your Naming

You just named something: a function, a method or a class. Is there a change around the code that would make the name wrong? What if you copy+paste the named piece of code to another project? Would you need to change the name?

# Bad
function start_yellow_cars(cars) { ... } # The function doesn't know or care about the color
yellow_cars = ...
start_yellow_cars(yellow_cars)

# The change that would highlight the wrong naming
# while keeping the code completely functional
function start_yellow_cars(cars) { ... }
my_cars = ...
start_yellow_cars(my_cars)


# Good
function start_cars(cars) { ... }
yellow_cars = ...
start_cars(yellow_cars)

Common Naming Mistakes Observed

  1. Naming a data structure with “JSON” in name.
  2. Argument vs Parameter

Tooling

I highly recommend using IDEs that “understand” the code enough to be able to refactor/rename (classes, methods, functions, parameters) as opposed to text editors which can not assist with renaming to the same extent.


Hope this helps. Happy naming!

Event Loop for Beginners

The aim of the post is to give a simple, concise, and high level description of the event loop. I’m intentionally leaving out many details and being somewhat imprecise.

If you need detailed picture, follow the links in this post. In this case, I recommend reading this short post first.

Why Event Loop?

To deal with concurrency (multiple “things” need to be happening at what looks like the same time), there are few models. To discuss the models, we need to know what a thread is.

Roughly, a thread is a sequence of computing instructions that runs on a single CPU core.

Roughly, Event Loop manages on which tasks the thread works and in which order.

Queue

The queue contains what’s called in different sources messages, events, and tasks.

Message/event/task is a reference to a piece of code that needs to be scheduled to run. Example: “the code responsible for handling the click of button B + full details of the click event to pass to the code”.

Event Loop

  1. The queue is checked for new tasks. If there is none, we wait for one to appear.
  2. The first task in the queue is scheduled and starts running. The code runs till completion. In many languages, await keyword in the code counts as completion and everything after await is scheduled to run later – new task in the queue.
  3. Repeat from number 1. That’s the loop. It’s called Event Loop because it processes events from the queue in a loop.

Adding Events to the Queue

Tasks are added to the queue for two reasons:

  1. Something happened (user clicked on a button, network packet arrived, etc).
  2. The code that was running in step 2 of the event loop scheduled something to run later.

See Also

  1. Event Loop documentation at MDN.
  2. What is the difference between concurrency and parallelism? at StackOverflow

Hope this helps with high level understanding of Event Loop. Have a nice day!

Arguments and Parameters

These two words are used interchangeably. Please don’t. They mean different things. Here is my concise explanation.

Argument

A value passed into a function/method during invocation.

my_func(arg1, arg2)

Additional names for “argument” are “actual argument” and “actual parameter”.

Parameter

A name of a variable in the function/method definition. During invocation, the variable is used in the function/method body to refer to the value of the passed argument.

F my_func(param1, param2) {
  ...
  # Using param1 and param2 for a computation
  ...
}

Additional name for “parameter” is “formal argument”.

Tip – Parametrize

If you struggle to remember which one is which, this might help: when you “parameterize” a piece of code, you add parameters to the code. Then you have the code with the parameter used in it, with the first occurrence in the function/method definition.

# Initial version

echo("Hello, Joe")

# Parametrized version. "name" is a parameter.

F hello(name) {
  echo("Hello, ${name}")
}

See Also


Hope this helps! Have a nice day!


Updates after Reddit discussion:

  • I never asked the difference as an interview question. If I would:
    • Getting this wrong – tiny negative point
    • Not understanding why using correct terminology matters – big negative point
    • Understanding the difference and using these words interchangeably (knowingly incorrectly) – huge negative point
    • Providing fake facts to support your opinion that these words are interchangeable – huge negative point
  • Explaining why using correct terminology matters is out of scope of this post

The new Life of tap()

Background

I’m designing and implementing Next Generation Shell, a programming language (and a shell) for “DevOps” tasks (read: running external commands and data manipulation are frequent).

I came across a programming pattern (let’s call it P) as follows:

  1. An object is created
  2. Some operations are performed on the object
  3. The object is returned from a function (less frequently – stored in a variable)

P Using Plain Approach

The typical code for P looks in NGS like the following:

F my_func() {
  my_obj = MyType()
  my_obj.name = "blah"
  my_obj.my_method(...)
  my_obj  # last expression is evaluated and returned from my_func()
}

The above looks repetitive and not very elegant. Given the frequency of the pattern, I think it deserves some attention.

Attempt 1 – set()

In simpler but pretty common case when only assignment to fields is required after creating the object, one could use set() in NGS:

F my_func() {
  MyType().set(name = "blah")
}

or, for multiple fields:

F my_func() {
  MyType().set(
    name = "blah"
    field2 = 100
    field3 = "you get the idea"
  )
}

Side note: parameters to methods can be separated by commas or new lines, like in the example above.

I feel quite OK with the above but the cons are:

  1. Calling a method is not supported (unless that method returns the original object, in which case one could MyType().set(...).my_method())
  2. Setting of fields can not be interleaved in a straightforward manner with arbitrary code (for example to calculate the fields’ values)

Attempt 2 – tap()

I’m familiar with tap() from Ruby. It looked quite useful so NGS also had tap() for quite a while. Here is how P would look like in NGS when implemented with tap():


F my_func() {
  MyType().tap({
    A.name = "blah"
    A.my_method()
  })
}

Tap takes an arbitrary value, runs the given callback (passing that value as the only argument) and returns the original value. It is pretty flexible.

Can’t put my finger on what’s exactly is bothering me here but the fact is that I was not using tap() to implement P.

Attempt 3 – expr::{ … }

New Life of tap()

This one is very similar to tap() but it is syntactically distinct from tap.

F my_func() {
  MyType()::{
    A.name = "blah"
    # arbitrary code here
    A.my_method()
  }
}

I think the main advantage is that P is easily visually distinguishable. For example, if you only want to know the type of the expression returned, you can relatively easy skip everything between ::{ and } . Secondary advantage is that it’s a slightly less cluttered than tap().

Let’s get into the details of how the above works.

Syntax

  1. MyType() in our case is an expression. Happens to be a method call which returns a new object.
  2. :: – namespace field access operator. Typical use case is my_namespace::my_field.
  3. { ... } – anonymous function syntax. Equivalent to a function with three optional parameters (A, B, and C, all default to null).

Note that all three syntax elements above are not unique to this combination. Each one of them is being used in other circumstances too.

Up until recently, the :: syntax was not allowing anonymous function as the second argument. That went against NGS design: all methods should be able to handle as many types of arguments as possible. Certainly limiting arguments’ types syntactically was wrong for NGS.

Semantics

In NGS, any operator is transformed to a method call. :: is no exception. When e1::e2 is encountered, it is translated into a call to method :: with two arguments: e1 and e2.

NGS relies heavily on multiple dispatch. Let’s look at the appropriate definition of the :: method from the standard library:

F '::'(x, f:Fun) {
  f(x)
  x
}

Not surprisingly, the definition above is exactly like the definition of F tap() ... (sans method and parameters naming).

Examples of expr::{ … } from the Standard Library

# 1. Data is an array. Each element is augmented with _Region field.
data = cb(r)::{
  A._Region = ConstIter(r)
}


# 2. push() returns the original object, which is modified in { ... }
F push(s:Set, v) s::{ A.val[v] = true }


# 3. each() returns the original object.
# Since each() in { ... } would return the keys() and not the Set,
# we are working around that with s::{...}
F each(s:Set, cb:Fun) s::{ A.val.keys().each(cb) }


# 4. Return what c_kill() returns unless it's an error
F kill(pid:Int, sig:Int=SIGNALS.TERM) {
  c_kill(pid, sig)::{
    A == -1 throws KillFail("Failed to kill pid $pid with signal $sig")
    A != 0 throws Error("c_kill() did not return 0 or -1")
  }
}

Side note: the comments are for this post, standard library has more meaningful, higher level comments.

A Brother Looking for Use Cases

While changing syntax to allow anonymous function after ::, another change was also made: allow anonymous function after . so that one could write expr.{ my arbitrary code } . The whole expression returns what the arbitrary code returns. Unfortunately, I did not come across (or maybe haven’t noticed) real use cases. The appropriate . method in the standard library is defined as follows:

F .(x, f:Fun) f(x)

# Allows
echo(5.{ A * 2 })  # 10

Have any use cases which look less stupid than the above? Let me know.

The Pseudo Narrow Waist in Unix

Background

This is a pain-driven response to post about Narrow Waist of Unix Architecture. If you have the time, please read that post.

The (very simplified and rough) TL;DR of the above link:

  1. The Internet has “Narrow Waist”, the IP protocol. Anything that is above that layer (TCP, HTTP, etc), does not need to be concerned with lower level protocols. Each piece of software therefore does not need to concern itself with any specifics of how the data is transferred.
  2. Unix has “Narrow Waist” which is text-based formats. You have a plethora of tools that work with text. On one side of of Narrow Waist we have different formats, on another side text manipulating tools.

I agree with both points. I disagree with implied greatness of the Unix “design” in this regard. I got the impression that my thoughts in this post are likely to be addressed by next oilshell blog posts but nevertheless…

Formats

Like hierarchy of types, we have hierarchy formats. Bytes is the lowest level.

Bytes

Everything in Unix is Bytes. Like in programming languages, if you know the base type, you have a certain set of operations available to you. In case of Bytes in Unix, that would be cp, zip, rsync, dd, xxd and quite a few others.

Text

A sub-type (a more specific type) of Bytes would be Text. Again, like in a programming language, if you know that your are dealing with data of a more specific type, you have more operations available to you. In case of Text in Unix it would be: wc, tr, sed, grep, diff, patch, text editors, etc.

X

For the purposes of this discussion X is a sub-type of Text. CSV or JSON or a program text, etc.

Is JSON a sub-type of Text? Yes, in the same sense that a cell phone is a communication device, a cow is an animal, and a car is a transportation device. Exercise to the reader: are this useful abstractions?

Cow is an animal

The Text Hell

The typical Unix shell approach for working with X are the following steps:

  1. Use Text tools (because they are there and you are proficient wielder)
  2. One of:
    1. Add a bunch of fragile code to bring Text tools to level where they understand enough of X (in some cases despite existing command line tools that deal specifically with X)
    2. Write your own set of tools to deal with the relevant subset of X that you have.
  3. Optional but likely: suffer fixing and extending number 2 for each new “corner case”.

The exception here are tools like jq and jc which continue gaining in popularity (for a good reason in my opinion). Yes, I am happy to see declining number of “use sed” recommendations when dealing with JSON or XML.

Interestingly enough, if a programmer would perform the above mentioned atrocities in almost any programming language today, that person would be pointed out that it’s not the way and libraries should be used and “stop using square peg for round hole”. After few times of unjustified repetition of the same offense, that person should be fired.

Square peg / round hole

Somehow this archaic “Unix is great, we love POSIX, we love Text” approach is still acceptable…

Pipes Text Hell

  1. Create a pipe between different programs (text output becomes text input of the next program)
  2. Use a bunch of fragile code to transform between what first program produces and the second one consumes.

Where Text Abstraction is not Useful

Everywhere almost. In order to do some of the most meaningful/high-level operations on the data, you can’t ignore it’s X and just work like it is Text.

Editing

The original post says that since the format is Text, you can use vim to edit it. Yes you can… but did you notice that any self respecting text editor comes with plugins for various X’s? Why is that? Because even the amount of useful “text editing” is limited when all you know you are dealing with Text. You need plugins for semantic understanding of X in order to be more productive.

Wanna edit CSV in a text editor without CSV plugin? OK. I prefer spreadsheet software though.

Have you noticed that most developers use IDEs that “understand” the code and not Notepad?

Lines Count

Simple, right? wc -l my.csv. Do you know the embedded text in quotes does not have newlines? Oops. Does it have header line? Oops.

Text Replacement

Want to try to rename a method in a Java program? sed -i 's/my_method/our_method/g' *.java, right? Well, depends on your luck. I would highly recommend to do such kind of refactoring using an IDE that actually understands Java so that you rename: only specific method in a specific class as opposed to unfortunately named methods and variables, not to mention arbitrary strings.

Search / Indexing

Yep… except that understanding of the semantics helps here quite a bit. That’s why you have utilities which understand specific programming languages that do the indexing.

Conclusion

I do not understand the fascination with text. Still waiting for any convincing arguments why is it so “great” and why the interoperability that it provides is not largely a myth. Having a set of tools enabling one to do subpar job each time is better than not having them but is it the best we can?

My previous dream of eradicating text where it does not make sense (my blog post from 2009) came true with HTTP/2. Apparently I’m not alone in this regard.

Sorry if anything here was harsh. It’s years of pain.

Clarification – Layering

Added: 2022-02-07 (answering, I hope, https://www.reddit.com/r/ProgrammingLanguages/comments/t2bmf2/comment/hzm7n44/)

Layering in case of IP protocol works just fine. Implementer of HTTP server really does not care about the low level transport details such as Ethernet. Also the low level drivers don’t care which exactly data they deliver. Both sides of the Waist don’t care about each other. This works great!

My claim is that in case of the Text Narrow Waist, where X is on one hand of and the Text tools are on the other, there are two options:

  1. Tools ignore X and you have very limited functionality you get out of the tools.
  2. Tools know about X but then it’s “leaky abstraction” and not exactly a Narrow Waist.

That’s why I think that in case of Text, the Narrow Waist is more of an illusion.


Have a nice week!

On Accidental Serialization Formats

Let’s talk about the “just separate with comma and stick it into one field” type of serialization.

You had two strings (abc and def) and you joined them with a separator. What do you have now? One string with two elements, right? Right, abc,def. Well… two or more actually, depending on how many times the chosen separator occurred in the original strings: if they were a,bc and def, you’ve got a,bc,def, which is 3 elements according to our format. Oops. Leaving out the question whether leading and trailing spaces are significant.

Wanna add escaping for the separator then? a,bc and def are now serialized as a\,bc,def. Now the parsing became more complex. You can’t just split the string by the separator (you would get 3 elements: a\ and bc and def. You need to scan the serialized data, considering escaping when splitting. You also need to remove the escaping character from the result. How about escaping the escape character? If original data is a\bc, it is serialized as a\\bc). Yet another something not to forget.

Don’t like escaping then? How about encoding like in URL? a,bc becomes a%2Cbc. You can now once again split the string by the separator character… assuming it was encoded. Which characters you encode anyway? If you encode all ASCII characters, the result is 3 times the original and is completely unreadable. It least you are “safe” with regards to separator now, it is encoded for sure so no split problems. You have to add a decoding routine now though.

If your serialized thing goes into a database, consider how indexing would work. It probably won’t. Maybe you should model your domain properly in the database and not serialize at all. Hint: if the values ever need to be treated differently/separately by the database, they go into different cells/rows/columns/fields, not one. There are very rare exceptions. Notable exception is the ability of databases to handle JSON fields (examples: MySQL, PostgreSQL). Note that this capability can fit or not fit your use case.

Want to satisfy your artistic needs and do something clever about the serialization? Do it at home then please. Don’t waste time that your colleagues could use on something more productive than dealing with your custom format.

Strong advice: don’t do custom serialization format, use existing serialization formats and libraries.


Seen something to add to the above? Leave a comment!

Failed Stealing from Python

I made a mistake. Hope you will learn something from it.

Mental Shortcuts

Heuristic, tl;dr for the purposes of this article – mental shortcut. The brain chooses to do less thinking to save energy. It relies on simple rules to get the result. The result is correct… some times.

I took a mental shortcut when working on my own programming language, Next Generation Shell. It was a mistake.

Additionally, I have ignored the uneasy but very vague feeling that what I’m doing is not 100% correct. From my experience I knew I shouldn’t ignore it but I still did it. Another mistake.

I “thought”

Below are heuristics that led to the wrong decision.

Copying features from popular languages is pretty “safe”. After all, “everybody” is using the language and it’s OK. Social proof. Wrong. Everybody does mistakes. Popular languages have design issues too.

It’s OK to copy a feature because it’s very basic aspect of a language. Nope. Python messed up arguments passing. And I copied that mess.

The Fix

Python 3.8 has the fix. I have mixed feelings about it. Still not sure how I should fix it in NGS.

Takeaway

Beware of mental shortcuts. There are situations where these are not acceptable. The tricky part is to detect that you are using a mental shortcut in a situation where it’s not appropriate. I hope that with awareness and practice we can do it.

Also note that your $job is most likely paying you to not take mental shortcuts.