Ted Kaminski writes essays on software design, abstraction, type systems and language design, including the expression problem and function coloring.
HN commenters cite it for the expression problem and the wizard-versus-engineer divide, and one links its critique of SOLID principles.
This is yet another example of the divide between wizarding and engineering[1]. When you're a small startup, what matters is the expressiveness of your language, and the ability do do a lot of things very very quickly. Type safety, performance, readability, those things don't matter. You're just a bunch of engineers who know the whole codebase inside out, you're pretty certain of what you're doing. In short, you're wizarding. If you grow big enough, this approach slows you down greatly, and you need to switch to engineering. You sacrifice some speed for making the codebase more understandable to a larger group of people, you can no longer assume everyone knows all the code, you write unit tests, need types and dislike metaprogramming because of the confusion it creates. This is why languages like Python, Ruby, Lisp or Smalltalk are amazing for small startups, but Java is what enterprises use. They're different ends of the wizarding/engineering spectrum. I wish there was a language that let you move gradually from one end to the other, exactly when you need to.
[1] https://www.tedinski.com/2018/03/20/wizarding-vs-engineering.html
Sure, I mean more like there'd be some interface ByteStream, and probably another interface TerminalInput extends ByteStream; TerminalInput would then contain the especially terminal-related signals (e.g. sigwinch). If a program doesn't declare that it wants a full TerminalInput for stdin, the sigwinches would be dropped. If it declares it wants one but the environment can't provide one (e.g. stdin is closed, stdin is a file, etc.) it would be an error from execve().
Other signals would be provided by other resources, ofc; e.g. sigint should probably be present in all programs.
---
In general, my ideal OS would have look something like [0], but with isolation between the objects (since they're now processes), provisions for state changes (so probably interfaces would look more like session types), and with a type system that supports algebraic data types.
[0]: https://www.tedinski.com/2018/02/20/an-oo-language-without-inheritance.html
You need just the right amount of expressivity in a language, so that it is hard to abuse, but still allows writing easy to use libraries.
Java has went over this evolution, implemented generics, lambdas, etc and I believe it strikes a very good balance in not being overly complex (just look at the spec - it's still a very small language, compared to its age, unlike C++ or C#).
Go tried to re-invent this evolution, without having learnt Java's lessons. They will add more and more features until their "simple" will stop applying (though I personally believe that their simple was always just simplistic), simply because you need some expressivity for better libraries, which will later on actually simplify user code.
Also relevant: https://www.tedinski.com/2018/01/30/the-one-ring-problem-abstraction-and-power.html
I had the same opinion with async/await, that it's nice to know a function performs IO and will wait before continuing. Makes it clearer when to use Promise.all to make multiple requests in parallel and wait for all of them to finish before continuing (faster than making calls sequentially).
I kind of wish the languages I use had Haskell's IO monad too, to separate functions in terms of the type system, but that's slightly different.
You might like this article (which is my personal favourite about function colouring). https://www.tedinski.com/2018/11/13/function-coloring.html
See also: https://www.tedinski.com/2018/11/06/concurrency-models.html which is the first article I've ever seen that says that `m:n`-style-green-threads are bad and good at the same time.
TL;DR - he says that "doParallel" and "doConcurrently" are separate operations with distinct semantics that designers of programs must care about and that conflating the two (especially the common "doConcurrently-and-often-but-not-always-in-parallel") is one of the most common causes of bugs in programs that need to make progress in multiple threads of execution.
With the advent of coverage based fuzzing, and how well supported it is in Go, what am I missing from not using one of the property based testing libraries?
https://www.tedinski.com/2018/12/11/fuzzing-and-property-testing.html
Like, with the below fuzz test, and the corresponding invariant checks, isn't this all but equivalent to property tests?
https://github.com/ncruces/aa/blob/505cbbf94973042cc7af4d6be6266454bb2d864e/aa_test.go#L374-L403
https://github.com/ncruces/aa/blob/505cbbf94973042cc7af4d6be6266454bb2d864e/util_test.go#L5-L28