I'm sceptical of his argument that "we've gone from dynamic being trendy back to typed(java), because 'people' had to maintain dynamic codebases".
An equivalent but probably equally not-the-real-explanation argument would be that we've gone from an era of opportunity into an era of oligopoly as the internet titans have emerged and so the "coolest kids" who everyone cargocults have gone from being fast-growing startups to members of the big-5 elite. Kind of the same as his argument just with detail on who his "people" are and why, but it changes the implications.
I think it's more as he touches on that the two are converging. I think eventually some sort of pluggable typing will win (the "proofs" on your system won't be a single compilation pass but maybe different typing for different parts of the system, and specific proofs run between compile and runtime as the two blur), which will look more like gradual typing.
According to the definitions provided, it seems that engineering is the wrong approach for the vast majority of systems (especially popular web-based platforms). We should be leaning more towards wizarding.
Most systems change all the time. So long as a company needs executives to make decisions and steer in different directions depending on the economic conditions, companies also need the flexibility to change their code.
Even the Linux Kernel which is now decades old is still being changed all the time. If you use tools which assume that every line of code you write is not going to change, then unless you're programming an aircraft or a medical hardware device you're probably using the wrong tools.
You should not assume that just because some low level module is deeply nested within the code, that it means that it should not be changed or thrown out.
That's why I prefer dynamically typed languages for web systems; they start with the rigth assumption about the ever-evolving nature of the project.
If JavaScript was designed to be statically typed from the beginning, web browsers would not have attained the usefulness or popularity that they have today.
I disagree with your assertion that dynamic languages are easier to change. In my experience programs written in a typed language are much easier to change, because IDEs can exactly tell you how something interconnects (and thus where breaks might happen) and the compiler can give you some confidence that you didn't miss anything, or alternatively throw an error if you did miss something.
You can partially provide the same benefits in dynamic languages with tests, but at that point you're paying the same cost as with a type system.
Statically typed languages encourage bad programming practices precisely because they make it easier to track type references across many files.
Ideally, types should not traverse too many files; there should be a clear class hierarchy and each level should provide more abstraction. Also it's better to encourage passing simple types like strings, numbers or clones of objects instead of active instances... Because if an instance of a class is referenced in many parts of the code, it's difficult to track which part of the code was responsible for changes made to that instance; also it's harder to maintain your train of thought when traversing many files to debug a simple operation related to a specific instance type.
On a related note, I've noticed at multiple companies that when they force developers to use specific IDEs which make it easy to find stuff, the directory structure of the projects tends to suffer (since existing developers don't rely on the directory structure to find things; so they stop caring about it) - This makes it harder for newcomers to make sense of the code and makes the project totally dependent on the IDE.
I feel like "each level should provide more abstraction" is at odds with "encourage passing simple types like strings, numbers".
Also, I'd go further than "pass clones of objects instead of active instances" all the way to "only use mutable objects when performance demands it, and keep the scope of the mutability small".
Agreed. In my experience, the cost of reasoning about a code base is much higher than the cost to type the code in an editor. And reasoning about a code base in a language like Java using an IDE like IntelliJ is much less costly than reasoning about a large code base in a language like Perl using VIM and the terminal.
That's why I prefer dynamically typed languages for web systems; they start with the rigth assumption about the ever-evolving nature of the project.
I agree with your assessment that we need to design for evolution.
I disagree with your assessment that dynamic languages provide that. Dynamic languages certainly do hit a sweet spot for fast iteration on small projects. As soon as your project gets large or long running, the assurances that static types provide get really nice. Witness how much effort has gone into building type systems for dynamic languages: Typescript (MS) is exploding and had vigorous competition from Flow (Facebook). Python has mypy plus a bunch of corporate backing: MonkeyType (Instagram) and PyAnnotate (Dropbox).
I feel much more comfortable aggressively refactoring in static languages.
To add to you point, more-and-more PHP is growing support for types with every major release. Starting with 5.0, then 5.1, 5.4, 7.0, 7.1, and 7.2 – each of the aforementioned releases added support for greater typing hinting in function definitions and support for specifying types.
Another problem is highlighted by Joe Armstrong and Rich Hickey, which is that as soon as you move beyond the confines of a single program and start dealing with a system of programs, type guarantees become less relaible. So you have these types that may make complete sense in your program's world, but they drift out of sync as the larger system evolves. This is a general problem with any sort of proof mechanism. I still like types and think they can be useful, but they are only a single tool in designing a robust system. They have limitations and drawbacks like everything else.
types that may make complete sense in your program's world, but they drift out of sync as the larger system evolves.
At the risk of being inflammatory, I would say that you've been looking at poorly designed systems.
A running joke about Google engineers is that all they do is shuffle data from one ProtoBuf to another. It's largely true. The upshot of that is that every single service/system has common, typed definitions of the data structures they use and require. Data never drifts out of sync.
To be sure, a lot of the problems I dealt with there were around data structure migration on large systems — but there was never any uncertainty about the structure of the data itself.
I like JSON APIs, but I think JSON especially contributes to people getting hand-wavy and casual about the structure of data across systems in stupid ways (see NoSQL vs SQL).
I don't think that's inflammatory at all. I tend to agree with you that a very good engineering organization would enforce data-first design and maintenance practices and build types around that. But unfortunately a lot of places don't operate that way and so my comment was meant in more of a general sense, not in a "types cannot possibly be useful at a systems level" way.
There are a lot of overlapping distinctions, some blurry, I think. He chooses some for wizardry vs engineering but I don't know whether they're best. For example he puts "magic"=implicit, but I would normally say highly implicit code is "engineering" because it implies that someone has understood and explored the problem enough to write a very tuned framework or library. I think reasons for putting very implicit code in with wizardry could be that it might be that the person using it doesn't understand it, and that it's somewhat in opposition to strong typesystems
wizardry/engineering
high uptime/downtime possible
efficiency important/efficiency not important
large codebase/small codebase
not implicit/implicit (I disagree most with this one)
problem is understood/code is exploratory (I think this is the most important distinction)
high specialism/low specialism of coders
changes slowly/changes quickly
typed/untyped
I would say websites are engineering because the problem space is well understood, they need high uptime and they tend to be written by specialists (React guy, django guy etc).
The way I think of it is "wizardry" has good initial velocity (as it relates to change), but velocity decreases with time, whereas for "engineering" velocity is terrible at first (initial investment), improves a bit over time, but most importantly it settles to a constant level.
That's why I prefer dynamically typed languages for web systems; they start with the rigth assumption about the ever-evolving nature of the project.
The inverse can be said: a statically typed language makes it easier to replace parts later, do automatic refactorings, etc., and know you got everything right.
Dynamic just let you have less ceremony (no type definitions) but forces you to keep this all in your head (you still don't want to pass the wrong type of thing to the wrong receiver) and doesn't give you any assurances.
With type inference and/or autocomplete, statically typed languages are faster to write and easier to get right than dynamic.
Gradual typing does not seem to work very well. It requires a runtime check every time you pass data from a "dynamically-typed" to a "statically-typed" part of the program, and then a conversion step from statically-typed to tagged data for the converse. These things eat into performance, in the end it's no better than what dynamic types would give you, and a lot worse than what one could achieve with static types.
Comments
I'm sceptical of his argument that "we've gone from dynamic being trendy back to typed(java), because 'people' had to maintain dynamic codebases".
An equivalent but probably equally not-the-real-explanation argument would be that we've gone from an era of opportunity into an era of oligopoly as the internet titans have emerged and so the "coolest kids" who everyone cargocults have gone from being fast-growing startups to members of the big-5 elite. Kind of the same as his argument just with detail on who his "people" are and why, but it changes the implications.
I think it's more as he touches on that the two are converging. I think eventually some sort of pluggable typing will win (the "proofs" on your system won't be a single compilation pass but maybe different typing for different parts of the system, and specific proofs run between compile and runtime as the two blur), which will look more like gradual typing.
According to the definitions provided, it seems that engineering is the wrong approach for the vast majority of systems (especially popular web-based platforms). We should be leaning more towards wizarding.
Most systems change all the time. So long as a company needs executives to make decisions and steer in different directions depending on the economic conditions, companies also need the flexibility to change their code.
Even the Linux Kernel which is now decades old is still being changed all the time. If you use tools which assume that every line of code you write is not going to change, then unless you're programming an aircraft or a medical hardware device you're probably using the wrong tools.
You should not assume that just because some low level module is deeply nested within the code, that it means that it should not be changed or thrown out.
That's why I prefer dynamically typed languages for web systems; they start with the rigth assumption about the ever-evolving nature of the project.
If JavaScript was designed to be statically typed from the beginning, web browsers would not have attained the usefulness or popularity that they have today.
I disagree with your assertion that dynamic languages are easier to change. In my experience programs written in a typed language are much easier to change, because IDEs can exactly tell you how something interconnects (and thus where breaks might happen) and the compiler can give you some confidence that you didn't miss anything, or alternatively throw an error if you did miss something.
You can partially provide the same benefits in dynamic languages with tests, but at that point you're paying the same cost as with a type system.
Statically typed languages encourage bad programming practices precisely because they make it easier to track type references across many files.
Ideally, types should not traverse too many files; there should be a clear class hierarchy and each level should provide more abstraction. Also it's better to encourage passing simple types like strings, numbers or clones of objects instead of active instances... Because if an instance of a class is referenced in many parts of the code, it's difficult to track which part of the code was responsible for changes made to that instance; also it's harder to maintain your train of thought when traversing many files to debug a simple operation related to a specific instance type.
On a related note, I've noticed at multiple companies that when they force developers to use specific IDEs which make it easy to find stuff, the directory structure of the projects tends to suffer (since existing developers don't rely on the directory structure to find things; so they stop caring about it) - This makes it harder for newcomers to make sense of the code and makes the project totally dependent on the IDE.
I feel like "each level should provide more abstraction" is at odds with "encourage passing simple types like strings, numbers".
Also, I'd go further than "pass clones of objects instead of active instances" all the way to "only use mutable objects when performance demands it, and keep the scope of the mutability small".
Agreed. In my experience, the cost of reasoning about a code base is much higher than the cost to type the code in an editor. And reasoning about a code base in a language like Java using an IDE like IntelliJ is much less costly than reasoning about a large code base in a language like Perl using VIM and the terminal.
I agree with your assessment that we need to design for evolution.
I disagree with your assessment that dynamic languages provide that. Dynamic languages certainly do hit a sweet spot for fast iteration on small projects. As soon as your project gets large or long running, the assurances that static types provide get really nice. Witness how much effort has gone into building type systems for dynamic languages: Typescript (MS) is exploding and had vigorous competition from Flow (Facebook). Python has mypy plus a bunch of corporate backing: MonkeyType (Instagram) and PyAnnotate (Dropbox).
I feel much more comfortable aggressively refactoring in static languages.
To add to you point, more-and-more PHP is growing support for types with every major release. Starting with 5.0, then 5.1, 5.4, 7.0, 7.1, and 7.2 – each of the aforementioned releases added support for greater typing hinting in function definitions and support for specifying types.
Another problem is highlighted by Joe Armstrong and Rich Hickey, which is that as soon as you move beyond the confines of a single program and start dealing with a system of programs, type guarantees become less relaible. So you have these types that may make complete sense in your program's world, but they drift out of sync as the larger system evolves. This is a general problem with any sort of proof mechanism. I still like types and think they can be useful, but they are only a single tool in designing a robust system. They have limitations and drawbacks like everything else.
At the risk of being inflammatory, I would say that you've been looking at poorly designed systems.
A running joke about Google engineers is that all they do is shuffle data from one ProtoBuf to another. It's largely true. The upshot of that is that every single service/system has common, typed definitions of the data structures they use and require. Data never drifts out of sync.
To be sure, a lot of the problems I dealt with there were around data structure migration on large systems — but there was never any uncertainty about the structure of the data itself.
I like JSON APIs, but I think JSON especially contributes to people getting hand-wavy and casual about the structure of data across systems in stupid ways (see NoSQL vs SQL).
I don't think that's inflammatory at all. I tend to agree with you that a very good engineering organization would enforce data-first design and maintenance practices and build types around that. But unfortunately a lot of places don't operate that way and so my comment was meant in more of a general sense, not in a "types cannot possibly be useful at a systems level" way.
A robust system is typed. A robust ecosystem is typed. Everything else is evaluated and dependent upon some agency.
I'm not sure to what extent I believe this.
There are a lot of overlapping distinctions, some blurry, I think. He chooses some for wizardry vs engineering but I don't know whether they're best. For example he puts "magic"=implicit, but I would normally say highly implicit code is "engineering" because it implies that someone has understood and explored the problem enough to write a very tuned framework or library. I think reasons for putting very implicit code in with wizardry could be that it might be that the person using it doesn't understand it, and that it's somewhat in opposition to strong typesystems
wizardry/engineering high uptime/downtime possible efficiency important/efficiency not important large codebase/small codebase not implicit/implicit (I disagree most with this one) problem is understood/code is exploratory (I think this is the most important distinction) high specialism/low specialism of coders changes slowly/changes quickly typed/untyped
I would say websites are engineering because the problem space is well understood, they need high uptime and they tend to be written by specialists (React guy, django guy etc).
The way I think of it is "wizardry" has good initial velocity (as it relates to change), but velocity decreases with time, whereas for "engineering" velocity is terrible at first (initial investment), improves a bit over time, but most importantly it settles to a constant level.
Engineering is O(log(n)), wizardry is O(N*N).
That may be true, but immediately makes me think of Rich Hickey's "constants matter".
Mostly if they're different order of magnitude, and they're not.
The inverse can be said: a statically typed language makes it easier to replace parts later, do automatic refactorings, etc., and know you got everything right.
Dynamic just let you have less ceremony (no type definitions) but forces you to keep this all in your head (you still don't want to pass the wrong type of thing to the wrong receiver) and doesn't give you any assurances.
With type inference and/or autocomplete, statically typed languages are faster to write and easier to get right than dynamic.
Gradual typing does not seem to work very well. It requires a runtime check every time you pass data from a "dynamically-typed" to a "statically-typed" part of the program, and then a conversion step from statically-typed to tagged data for the converse. These things eat into performance, in the end it's no better than what dynamic types would give you, and a lot worse than what one could achieve with static types.