I’m interested in your comment about “using dynamic-untyped rather than well-typed because it helps you not worry about your own intelligence”. I use well-typed languages religiously precisely for that reason: I’m not smart enough to program in an untyped language without making far too many mistakes, and the type system protects me from my idiocy.
You misunderstand me. I don’t use the simpler products because otherwise I worry about my own intelligence. What I found was that in order to protect my ego, and because I thought I was smarter than I was, I consistently used software engineering tools and platforms that were complicated to the point of hurting my ability to program.
I will remove Go from the above post because I think it doesn’t fit; I dislike Go’s genericless type system because it constrains my programs and not because I’m thinking about it all the time. With regard to “well-typed” programming languages, the problem isn’t that they don’t help me write more correct code—they do. The problem is that they make me think about types to a degree that’s often unnecessary or irrelevant to solving the problem at hand, and while performing exploratory programming I have to deal with these type algebras that are a context switch from my ML or infrastructure problems. Most bugs are problems with semantics and not syntax, so normally thinking very hard about types turns out to be wasted effort in the long run.
Wrt typing, we have:
Strict/strong: c, go, java, erlang
Strict/weak: doesn’t exist right?
Dynamic/strong: python, elixir
I prefer dynamic and don’t particularly care about strong/weak since generally my programs don’t do what I want because they are semantically wrong, not syntactically.
I’m a big believer in “the types should constrain the semantics of my program so hard that there is only one possible program I could write, and it is correct”. Of course we have to sacrifice some safety for speed of programming; for many domains, being 80% sure that a feature is correct in 95% of the possible use cases is good enough to ship it. But in fact I find that I code *faster* with a type system, because it forces most of the thinking to happen at the level of the problem domain (where it’s easy to think, because it’s close to real life); and there are a number of ways one can extremely cheaply use the type system to make invalid states unrepresentable in such a way that you no longer have to test certain things (because there’s no way even to phrase a program that could be incorrect in those ways).
For a super-cheap example, if you know that a list is going to be nonempty, use a non-empty list structure to hold it. (A non-empty list can be implemented as a pair of a head and a list.) Then you can save all the time you might otherwise have spent on coding defensively against people giving you empty list inputs, as well as any time you might have spent testing against that particular corner case.
For another super-cheap example that is so totally uncontroversial that it probably sounds vacuous (but it is in fact the same idea of “represent what you know in the type system so that the language can help you”), don’t store lists of (key, value); store a dictionary instead, if you know that keys are unique. This tells you via the type system that a) keys are definitely unique, and b) various algorithms like trees or hashmaps can be used for efficiency.