At this point it's beyond a hill I'm willing do die on. I'm not really interested in discussing it. If you don't get it I probably don't want to talk to you about it, I might even think less of you as a software developer. The amount of pre-existing respect for someone I'd need to have before I engage in a good-faith discussion on "are types good" is pretty high.
edit: To clarify, I am on the "types good" side of things
And then, kind of orthogonal to that, there’s also static typing and dynamic typing.
I think strong, dynamic typing is just as good as strong static typing (weak typing is objectively bad)
As long as the types of your variables don’t change from under you, that’s good, but I’m also okay with the compiler / runtime figuring out what those types are for me.
With "dynamic typing" there is no compiler to figure out the types for you. There is only the runtime to print a stack trace (if you're lucky) and bail out. The whole point of types is to be able to check things without running the program, because exercising every possible code path becomes increasingly untenable at scale.
A side (but many would also say critical) benefit of types is that they act as a form of documentation that can never go stale (because they are enforced by the compiler). "Dynamic typing" does not offer this benefit whatsoever.
Many dynamically typed languages have static analyzers, but retain the flexibility of not needing to prescribe types up front (it’s impossible to know exactly what data structure you need at the start of a project)
That’s the crux of why I’m not all in on static typing all the time. (Especially for networked programs that expect a wide range of different kinds of input)
Having to prescribe the types I need before I actually need them goes against how I tend to build.
Sounds like you'd like C#. It does type inference with `var` and similar things, so you don't always have to specify types explicitly, but everything does have a type at compile time.
> Many dynamically typed languages have static analyzers, but retain the flexibility of not needing to prescribe types up front
Almost all static type systems have escape hatches that let you go dynamic when you really really need it. Also, I bet that most of your use cases for dynamic typing could be solved with a simple tagged union. Most people bemoaning the lack of flexibility of static typing just don’t know about how tagged unions can easily emulate dynamic typing when we need it, such that we rarely even need to reach for the actual escape hatches.
> (it’s impossible to know exactly what data structure you need at the start of a project)
Thankfully data structures are even easier to change with static typing: change it, gets a ton of type errors, fix them, done. With dynamic typing you run the risk of missing a call site.
> Having to prescribe the types I need before I actually need them goes against how I tend to build.
There’s type inference for that. I personally take advantage of it any chance I get.
Dynamic typing doesn't exclude the compiler from being able to detect some things that would cause errors at run time. It just means that these turn into compile time warnings, not compile errors.
In Common Lisp, it's common practice to not accept code unless all such warnings are gone.
That is static typing. You can add static types to a dynamic language, but then you introduce a compilation step. This is not an argument in favour of dynamic typing. If your language requires a compilation step anyway, then what you have is a static language with only one type: the ‘any’ type.
Every argument I’ve seen in favour of dynamic typing is some variation of “I like it this way.” They’re not technical arguments because dynamic typing is a strict subset of a statically typed language, equivalent to passing a flag to turn the type checker off.
Sure, not every compiler offers such a flag, but that is an argument against one particular static language (or group of languages), not an argument against static types itself.
> There is only the runtime to print a stack trace
Types are not exclusively for verification, and even if you decide it is, you can do a lot more with a type error than exiting the program.
That stance most people keep repeating is actually ridiculous. The most common usage of static type systems is to verify badly-written ad-hock dynamic ones that handle user errors.
> With "dynamic typing" there is no compiler to figure out the types for you. There is only the runtime to print a stack trace (if you're lucky) and bail out.
This is just incorrect. Plenty, if not most, dynamically-typed languages are compiled. Indeed even the idea that there's a clean dichotomy between compiled and interpreted is an outdated idea: I'm not aware of any production-quality language implementations which run tree-walk interpreters entirely without compilation. Modern "interpreters" typically compile to bytecode but in some cases even can compile to native code, they simply do so in a just-in-time manner. These compilation steps are quite capable of applying type systems.
For example, if you run a Python script, you can see the results of compilation cached in .pyc files (these will be either in the same directory as the source files, or in your __pycache__ folder, depending on your configuration).
> The whole point of types is to be able to check things without running the program, because exercising every possible code path becomes increasingly untenable at scale.
Is it? I would argue that the point of types is to report errors at the place where they occur, rather than doing the wrong thing silently and reporting an error elsewhere, or simply behaving incorrectly.
If a tree falls in a forest and nobody hears it fall, does it really fall at all? If a bug happens on a code path, and nobody exercises that code path, is there really a bug?
Dynamic vs static typing is a similar level of design decision as interpreted versus compiled. It's ridiculous to try to claim one is universally better than the other as they both have styles of programming and domains they are better suited for.
I would never want a statically typed, compiled awk for example.
I generally think large, production, multi-developer software should be written in a statically typed, compiled language.
Strong vs Weak typing is a closer to a debate about having a name spacing system or not. There is really no great reason to have weak typing or not use a name spacing system.
Mainstream dynamically typed languages are moving in statically typed direction. Python (Mypy, Pyright and others), Ruby (Sorbent, Rbs), JavaScript (Typescript, Flow). How many statically typed languages optionally removed types?
It's a bit sad to me that people can only imagine writing code for large production systems. Static types are hugely beneficial in this area which is why we see this trend.
Personally I think Python moving in the direction of static typing is a mistake, dynamic typing is very useful for domains where python is strongest: modeling, statistics, scientific computing etc. It's also part of the basic design of Python to be a dynamic language. Likewise Ruby, with it's heavy use of metaprogramming, also benefits tremendously from a lack of types.
But let me be clear: I do think statically typed languages are a very good idea for large production systems, I just personally do a lot of programming that's not for these systems.
> How many statically typed languages optionally removed types?
I wouldn't say "removed" types, but I've been in software along enough to remember when dynamic typing was the big hot thing and crusty old Java devs complained that we couldn't possibly live without static type annotations. I distinctly remember when C# introduced `var` (which is of course really type inference, not dynamic typing) to appeal to devs that were growing weary of types.
There's a great example in SICP of implementing a full object system in just a few lines of code that would not be possible to implement as elegantly in a statically typed language. Do I want that for a production system? No. But there is, or at least used to be, a world of computation being done for reasons other that quickly getting PRs pushed out to prod.
Ok, maybe my previous message was too radical.
Of course there's a space for dynamic types (I use Clojure in free time), but there's no reason for static types to obscure your program. When I talk about static typing I mean Ocaml, Haskell, Scala, F# where you program is statically typed but you'll rarely see any types.
> I can see both side of the arguments on many topics, such as vim vs. emacs, tabs vs. spaces, and even much more controversial ones. Though in this case, the costs are so low compared to the benefits that I just don't understand why anyone would ever choose not to use types.
> I'd love to know what I'm missing, but until then: Strong typing is a hill I'm willing to die on.
I genuinely want to know what I'm missing. I also outlined in the post all of the arguments I usually see in favor of not having types and why I disagree with them.
I'm happy, willing, and excited to hear what I'm missing.
Hey, I hear you, and I commend you for your good faith position of "I want to hear both sides". I'm just not there anymore. The arguments put forth ("the cost is worth it, it makes up for itself", etc) are arguments I've had a million times and I just no longer will engage.
Oh, apologies, I misread your comment. I thought you were saying that you won't engage in this discussion because of my strong stance on the topic. I'm a big fan of "Strong beliefs, weakly held".
> I just don't understand why anyone would ever choose not to use types.
Is there a language out there that gives you that choice?
I expect you mean choose not to use strong static typing as per the original piece? Compatibility is a pretty good reason. I'd like to see Javascript die in the fiery pits of hell as much as the next guy, but its positioning means it is almost inevitable that some system will make it the only reasonable choice for you to choose if you want to build for that system.
Typescript doesn't help. It adds static typing, but not strong typing.
Python and PHP both have type hints that are natively ignored by the language if present, but can be checked if you have enough of them, if that's what you mean.
The only other use I'm familiar with where strong types are used to describe the equivalent of static types, but we've made clear distinction between the two here. What other definition is there?
Usually: A strong type system does not allow types to change after being established. A weak type system allows types to change. This is also described in the original article.
C++ has casts. I don't regard those as making it a "weak" type system. I regard it as "strong type system with escape hatches", which is not the same thing. A strong type system should not be a straightjacket. (Languages that try to make types bulletproof tend to get used less than languages that allow escape hatches, and rightly so. The language designer never knows what the union of all use cases will be. Good language designers know that the user may have a case the designer didn't think of, and allow some flexibility to hopefully handle it.)
no. c++ allows you to convert a value of one type into a value of another type (in a very limited number of cases) but you cannot change the type of an object.
Casts don't change the type of an object. They might create a new object of another type from the first one.
edit: I guess what you want to say is that implicit casts make a type system weak. But even there there is plenty of wiggle room: I think that everybody agree that implicitly converting the string "1" to an integer is bad (which is not allowed in C++). Narrowing conversions are arguably bad (they are sometimes allowed in C++), but some other implicit conversions are hard to argue against (int to long for example, or derived to base).
> A strong type system does not allow types to change after being established. A weak type system allows types to change.
For dynamic types, sure, that's a reasonable enough definition. But it doesn't really make sense for static types: they're attached to expressions in your source code, not runtime values.
My personal notion of a strong type system is one built around function type signatures.
In order to have that, you need static typing of variables and constants, like most statically typed imperative languages have. But you also need a method to specify required input types and expected output types of all functions.
In a purely functional language like Haskell where there are only functions, and functions are first class and can be both inputs to or outputs from other functions, then the entire operation of the program is encapsulated in its function type signatures.
The entire flow of data and logic through the program can be type-checked by the compiler, and function implementations checked against their type signatures.
C++ has chosen a special place in the hellscape of language design by allowing you to silence the compiler using casts, where almost anything you do with the resulting object is undefined behaviour no diagnostic required.
Said UB is observationally indistinguishable from miscompilation under some toolchains under some optimisation controls.
It also has various bolt on weirdness like const doesn't mean the thing won't be changed by some other pointer so you can't constant propagate based on it, unless it's written on the global, at which point attempts to mutate it anyway may succeed under the usual UB challenges.
Maybe that's a "strong" type system, but you'd only define it like that if you started by taking C++ as axiomatically reasonable.
Static typing is already quite a help, as it makes whole categories of errors compile-time errors.
> I'd like to see Javascript die in the fiery pits of hell as much as the next guy
Part of the problem is that some of those "next guys" don't have the experience and/or vision to realize that it needs to die. Perhaps we should emulate Cato in our subsequent HN posts:
And furthermore, I consider it necessary that Javascript be replaced with WebAssembly so that we can use well-designed languages in the browser.
There's a specific scenario where I have more sympathy for weak typing, which is: I'm writing a small, focused utility by myself whose scope can never expand and whose code will never be collaborated on. I do have these, little shell scripts. I write them, they work, I move on and use it for 10 years. No types in there, fine whatever.
As soon as there's any complexity at all or another person involved that exception stops.
You are the other person. Come back in six months needing to make a change, and it's almost like you're new to the code.
Now, in general, I agree with you. If it's a one-off (or if it's really never going to need maintenance), and if it's small enough that you don't need types while writing it, then sure, do whatever is easiest at the time. But "never going to need maintenance" often turns out to be a lie, and when that time comes, you may be happy for some types as signposts to give a hint of what you were thinking all those months or years ago.
I want a shell scripting language that allows a max line length of (for example) 128 characters and a max script length of (for example) 128 lines, with no `source` equivalent. If the script expands to not be a "small, focused" single-file script it should just fail to run at all. Just enough for small utilities, but unusable for giant monstrosities.
... and unable to call external programs or communicate with external programs via pipes / sockets / etc.?
Otherwise, if you're unable to source another script, you could just have another script return a normal result in addition to a JSON blob of the environment variable changes the caller should make, which is probably worse than just allowing sourcing.
Pipes allow arbitrarily complex networks of communicating sequential processes. In some cases, networks of tiny CSPs are cleaner, but without discipline, they can rapidly become worse than huge monoliths.
That doesn't prevent the problem of length limits encouraging users to break their programs into networks of smaller programs communicating over pipes. I'm sure that sort of thing can be done well, but it certainly takes discipline.
Particularly as programs start out simple and organically grow, once they start hitting length limits, they're going to start evolving into locally-distributed computation. Maybe you get something nice like composable small unix-like utilities. However, I suspect there's a large overlap between the set of developers who would do that well and the set of developers who would still keep things nice and modular within a single process if they didn't have size limits.
Note that weak typing and dynamic typing are orthogonal. I believe you're expressing sympathy for strong dynamic typing: types are checked an enforced dynamically by the runtime (such as Python raising an exception if you try to add a string and an int), but there's no static type analysis done at compile / load time. Python, JS, Ruby, etc. are strong dynamically typed languages.
Also, type weakness is more a property of the runtime than the language, but for those languages with specifications, the language specification usually also specifies a large amount of behavior for a compliant runtime.
The C runtime, will gladly (with UB nasal demon caveats) allow you to treat an int as a pointer. There are static type checks, but no hard limits on the type-confused nonsense it will attempt to execute. C is statically typed, but weakly typed.
Java, C#, etc. are strong statically typed. There are static checks at compile time, and the runtimes do their best (modulo escape hatches) to use dynamic runtime type checks to plug the gaps in the static type systems. (For any sufficiently complex sound/consistent type system in a Turing-complete language, as per Godel's incompleteness theorem and the halting problem, there will be some programs that cannot statically type-check but still will never encounter a type error, regardless of input. So, in practice, there will always be escape hatches in the type system to allow programs that are correct but not provable. Hopefully most of these escape hatches are backed by dynamic runtime checks.)
Of course, these categories are a partially-ordered continuum, not clear binary distinctions. For some pairs of languages, you can say one's statically enforced properties are a superset of the other or that one runtime enforces a strict superset of the other's dynamic checks. However, it's often a case that you can't say one language strictly offers stronger static type guarantees or one runtime strictly enforces stronger dynamic type restrictions.
I’m aware of the difference, I was contemplating bash in my comment. Some people construe it as strongly typed though it’s a little ambiguous, here’s another discussion about it https://news.ycombinator.com/item?id=12704050
I'm also a pretty die-hard type-system user, but I've been programming a lot of Lisp and FORTH lately. FORTH is just bad at safety period. Lisp, on the other hand, I can see why there really isn't a need for types. The macro system means you can create arbitrary "compile-time" safety checks which is, IMO, more powerful than just a type system. That being said, I would still love a strongly, statically typed Lisp, or at least one with a strong macro type system (before y'all mention it, I'm not a fan of typed Racket).
I'm more into Scheme than CL, but am aware of Coalton. My current lisp is Gerbil: https://cons.io which already has a type annotation system and will be enhancing it for the next major release (v19).
Well, every programming language must necessarily have types to some degree, but they aren't necessarily static or strong type systems. Most Lisps do not have a static type system and even fewer have a "strong" type system.
> Well, every programming language must necessarily have types to some degree
The vast majority yes, that's why I was a bit confused :D
There some languages that have no types. The only thing I can think of though is a POSIX shell minus arrays. (Edit: Assembly and Forth are two better examples)
> Most Lisps do not have a static type system
True
> and even fewer have a "strong" type system.
Common Lisp, Emacs Lisp, Scheme, Hylang, Clojure, and Racket all feature strong typing. I'm curious where you have found this trove of weakly typed Lisp dialects
Lisps are not statically typed typically and "strongly" typed is poorly defined. I would not consider most Lisp's type systems to be strong or particularly expressive, but they don't need to be because it would hurt the main benefit to the language: meta-programming. I don't think a "strong" vs "weak" type system argument is particularly valuable here, yes most prevent you from inadvertently changing how a particular series of bytes is interpreted, but their aggregates do not have any type identity besides their own typically. This means you cannot typically express and enforce an aggregate's covariance or contravariance properties. There may be ways to do so, but most do not provide utilities for it.
Why dynamic typing is required for metaprogramming? D (and rust and c++ to a significantly lesser extent) for example has extremely strong metaprogramming capabilities while being statically typed.
I didn't say it was required, I said it hurts it in Lisps case. One of Lisps strongest features is extreme flexibility and homoiconicity. Strict typing would make it clunkier, though maybe it's worth it for some scenarios.
> I don't think a "strong" vs "weak" type system argument is particularly valuable here, yes most prevent you from inadvertently changing how a particular series of bytes is interpreted [...]
I think that interpretation of the "strong" vs "weak" scale is a valuable one within the context of the blog post. The post is at least partially about how type systems can help the programmer by making them aware of certain kinds of errors (it is also about when: static vs dynamic).
My understanding of the terms covariance and contravariance are a bit shaky. Could you provide an example in another language that you think cannot be expressed using the provided utilities of most Lisp's?
You also mentioned that you don't think most Lisp's have "expressive" type systems. What do you mean by that? When I think of a type system as being expressive, I think of it as having explicit rather than implicit types, which is unrelated to the issue of strongly vs weakly typed and static vs dynamic types. Do you mean more like how you can describe / constrain the relationships between types in certain strongly typed languages?
> I think that interpretation of the "strong" vs "weak" scale is a valuable one within the context of the blog post. The post is at least partially about how type systems can help the programmer by making them aware of certain kinds of errors (it is also about when: static vs dynamic).
Sure, agree. That's also the real point: type systems primarily exist to make semantically impossible computations unrepresentable in the language (at least, without some extra song and dance). To this end, Lisps have rather lackluster type systems, but they don't try to encode much of the languages semantics into a type system.
> My understanding of the terms covariance and contravariance are a bit shaky. Could you provide an example in another language that you think cannot be expressed using the provided utilities of most Lisp's?
I'm actually mostly concerned with type invariants (ex. List[int]), which don't really have much for representation in Lisps from what I've seen. Further, see above about using types to make compile-time assertions/checks about the behavior at runtime.
> You also mentioned that you don't think most Lisp's have "expressive" type systems. What do you mean by that? When I think of a type system as being expressive, I think of it as having explicit rather than implicit types, which is unrelated to the issue of strongly vs weakly typed and static vs dynamic types. Do you mean more like how you can describe / constrain the relationships between types in certain strongly typed languages?
"Expressive" is a nothing word that doesn't have concrete meaning in-context, similar to "strong" type system. That being said, I would say Rust, OCaml, and TypeScript have expressive type systems: the behavior of the language is largely encoded as types. The implicit vs explicit nature of types is not super consequential IMO, it has more to do with how you primarily represent semantic meaning. In lisp, it's symbols. In Rust, it's traits, enums, and structs (+ the affine types, but that's not relevant here).
In Lisps, and other dynamic languages, two or more objects that are not related by inheritance can be substitutable. This is very useful.
That makes inheritance-based covariance and contravariance largely moot.
You have substitutability-based covariance and contravariance (you can't get away from those) but they cannot be subdued declaratively.
E.g. if you're passing a callback function somewhere, which you know will pass widget objects to the callback, it's okay to use a function that was written to handle gadget objects, if widget objects are designed to substitute for gadget objects.
That's contravariance of substitutability.
Substitutability is the only thing that matters in the end. Declared inheritance doesn't ipso facto guarantee substitutability, and therefore declared covariance or contravariance, which are inheritance based, do not guarantee actual substitutability-based covariance or contravariance.
wait, are you saying that any sufficiently complicated lisp program contains an ad hoc, informally-specified, bug-ridden, slow implementation of a static type system?
In my experience whenever you put effort into making a post like this, outlining the common arguments you hear and your attempt at refuting them, people don't really pay attention and will just repeat the arguments you already addressed, ignoring your refutations.
Youre missing the perspective of a business owner and operator that has to hire many people with diverse patterns of thoughts and beliefs who will most likely work for their organization for less than 24 months.
As far as I'm aware, the generally accepted definition of strong typing is: some object in memory has a type, and the compiler (or interpreter) will not let you treat it as another type without some kind of explicit instruction to do so. This is in contrast to weak typing, where the compiler/interpreter will happily try to treat a Bar object as a Foo object if you do some Foo operation on it.
Memory safety is definitely not equivalent. Memory safety is more like not going past the end of an array, not doing use after free, etc. I agree that what I said is pretty much also the definition of type safety. But in my experience it is also the generally accepted definition of strong typing.
I think you mean "involves non-static typing"? If so, Python. You can't (for example) do something like '1 + "2"', because those types aren't compatible. It's dynamically, but strongly, typed.
Consider reading/watching Rich Hickey's talk Effective Programs [0][1] and Maybe Not [2][3]
In [0] in particular there're slides 22/23, here is part of the transcript but makes more sense with the slides on:
> And you can call them problems, and I'm going to call them the problems of programming. And I've ordered them here [...] I've ordered them here in terms of severity. And severity manifests itself in a couple of ways. Most important, cost. What's the cost of getting this wrong? At the very top you have the domain complexity, about which you could do nothing. This is just the world. It's as complex as it is.
>
> But the very next level is the where we start programming, right? We look at the world and say, "I've got an idea about how this is and how it's supposed to be and how, you know, my program can be effective about addressing it". And the problem is, if you don't have a good idea about how the world is, or you can't map that well to a solution, everything downstream from that is going to fail. There's no surviving this misconception problem. And the cost of dealing with misconceptions is incredibly high.
>
>So this is 10x, a full order of magnitude reduction in (?) severity before we get to the set of problems I think are more in the domain of what programming languages can help with, right? And because you can read these they'll all going to come up in a second as I go through each one on some slide so I'm not going to read them all out right now. But importantly there's another break where we get to trivialisms of problems in programming. Like typos and just being inconsistent, like, you thought you're going to have a list of strings and you put a number in there. That happens, you know, people make those kinds of mistakes, they're pretty inexpensive.
Weak typing has its uses in one use case. Transition between systems. Even then you usually want to know what the type is (unless you really do not care about validation errors).
The reality is I never get to choose. I prefer strong typing. But the projects I am on that ship sailed long ago 2 developers back who used this project as a resume builder.
You should legitimately try a project in notepad. Not Notepad++, specifically Notepad. Probably a very short project, but you should give it a shot. It'll give you a bit of a perspective. And some real appreciation for Notepad++.
I've worked with my fair share of vanilla JS devs who refuse to acknowledge the benefit of more statically typed variants of the language. Most of them bemoan the upfront cost of typing everything without realizing the benefits of it. I absolutely think less of them as developers.
Not liking typescript is not the same thing as denying that types are valuable.
TS has a very complex type system in exchange for relatively weak guarantees about correctness. That's a tradeoff you can choose to take or leave depending on the problem at hand, and holding that position is not the same as believing types have no value.
Unless you're talking about elm or rescript or something then sure sure. But usually when people say things like this they mean typescript.
This is pretty much my take, too. I don't hate the idea of TS, but TS is so painful to work with, and it's owned by a company I really don't like, too.
OP is saying it's provides weak guarantees which is a result of the fact they made it easy to adopt and implement among legacy untyped code. It's the opposite of painful to work with for typing systems, compared to something like Rust which has stronger guaruntees and more predictable patterns but hard requirements the full stack is type. The result in TS is plenty of "any"s everywhere and in legacy code some situations where it's mostly just a thing veneer of type safety.
But all of that makes plenty of sense because in the early days it was rare to work on a real life production JS project that was pure TS from day one in addition to having TS only libraries.
These days it's everywhere and the work has been done to move away from rampant anys in popular libraries. The tooling is also mature and it's rare to find major JS libraries not already packaged with types or a @types import. Turbo moving away from TS was the rare exception.
% cat a.ts
console.log('Hello, world!')
% time tsc a.ts
tsc a.ts 2.39s user 0.10s system 232% cpu 1.066 total
More than a second to compile a "hello, world" (or 2.4s in CPU time) is orders of magnitudes slower than any other compiler or interpreter that I know of. It's a ridiculous start-up time.
esbuild is not an alternative as it doesn't check types.
What I want is "GET /foo.js" to "just" compile TS "on the fly" in dev; it's a simple "just works" kind of setup, but not possible with TS.
Or for a simple system, just "roll your own":
for f in *.ts; tsc $f >|$f:r.js
watch-files *.js --run tsc
(for f in *.ts; tsc $f) | minify >production.js
Doesn't need to be shell, can be a simple JS script or whatever. You really shouldn't need millions of lines to call a compiler even in simple scenarios.
What exists now is an explosion of complexity to deal with all this and I guess these systems are "mature", kind of, but for a lot of systems it's massive overkill (and even for larger systems it's not particularly great IMO). Besides, it's really a bad fix for more fundamental problems.
Personally I wouldn't call TypeScript mature until compile times are roughly within the range of literally ever other compiler that has ever seen wide-spread adoption (and with that I don't mean "parallelize to 32 cores so my threadriper over 9000 can compile things in 0.1s). It doesn't need to be fast: just not a huge outlier.
You can still use tsc for type checking via editor language server and quality gates (with the noEmit flag) and get stupid fast compilation with swc or esbuild.
Explosion of complexity and "bad fix for fundamental problems" sounds a lot like you just have an axe to grind.
Many other popular languages have multiple build systems and tools to choose from as well; it isn't a particularly novel challenge.
I just want things to compile, with errors, like literally everything else works. It's really not a huge ask. I don't want complex bespoke setups with multiple compilers and background processes and whatnot. The classic JS/TS response is "here is the happy path with all this tooling, but if you want something outside of that then there is something wrong with you".
I guess the "axe" that I'm "grinding" is that I'm having a lot of difficulty using TS in a way that fits with my sensibilities and preferences. e.g. I don't like errors in my editor and prefer to explicitly call the compiler (for any environment). I suppose I could get all of this to work how I want it to with wrapper scripts or whatnot: but it's complex, time-consuming, and isn't needed for anything else.
Based on previous conversations about this at least one person will say something along the lines of "zomg what kind of crusty old backend unix beard doesn't use VSCode and want errors in their editor, you just need to get with the times as you're stuck in the past!!!1" but again: it works for literally everything else (including other compile-to-JS tools), and is not that "obscure" of a work-flow, IMHO, and we're back to a few paragraphs ago: "move outside the happy path and you're screwed".
I mentioned using tsc with the noEmit flag for quality gating, which is the same workflow you would use.
`tsc --noEmit a.ts` (possibly flipping the position of the flag, I forget if it is sensitive).
That'll check the types without spending unnecessary time compiling with the slower tooling.
From there, esbuild or swc binaries compile the code as desired. They use the same tsconfig file that tsc does, so no need for any extra complexity. They build so fast that you'll not mind having a separate command, I promise.
You could even combine the two into a simple one-liner if you wanted.
Compared to, say, java where you have to fight over maven or Gradle or ant, plus endless config options in XML or groovy or whatever, typescript really isn't much to complain about.
Hell, trying to set up a clojure full stack project is a nightmare of conflicting opinions over tooling between lein and shadowjs or whatever.
Of the big languages, C# is about the only one with a "one true way", and of the smaller ones, they simply haven't yet developed a big enough base to grow contentious enough to have divergent solutions.
Yep almost every (serious) TS dev only uses --noEmit for development by default because they use VSCode/IDEs as their typechecking engines and then have something like esbuild for delivering the content to the browser locally. I've never had performance issues with either step and I use both daily.
The raw output speeds aren't a very good 'practical real world example' as the OP described it.
If anything in dev ESLint (+ Copilot) is the slow ones that I sometimes noticed, but there is already Rust driven replacements maturing in the pipeline as we speak.
The performance of "tsc --noEmit a.ts" is identical, and it makes no difference vs. just typing "tsc a.ts", and it doesn't really solve "compile on demand" (with type checks) in any case.
I looked in to this some time ago, because I couldn't believe it was this slow, and tsc just has a huge startup cost. Once it gets going it's alright (I think? Don't quote me) but to get started takes a long time. It's actually already improved because not too long ago it was more like 3 seconds (probably because of the parallelisation, which is kind of cheating IMO).
I don't know about Java as I never really used it, but complex build systems are not unique to TS of course, but what they are in TS is mandatory for any reasonable experience because it works around the fact the compiler is so damn slow. That's a huge difference you can get started without too much effort, and you can do "smart" things fairly easily as I mentioned in my earlier comment.
> they simply haven't yet developed a big enough base to grow contentious enough to have divergent solutions.
C, C++, Go, Rust, Python, Ruby, PHP don't have a "big enough base"? Ehhh
And my entire point is that TS LACKS "divergent solutions". Because it's so slow lots of solutions are simply not practical.
If the tooling is a pain to use, slow, and owned by a crappy corporation, and I'm not going to use the types anyway, then I have even less of a reason to use it...
Typescript can be very complex - it can also be incredibly simple - it's up to you how far you want to take it.
Sure it can be an adventure to express something fully in it - but this is true for any type system I've seen.
And I haven't seen another type system that's integrated into a dynamic ecosystem as well (IMO python is 5 years behind in terms of type checking experience) and that lets you chose the level of type sophistication that makes sense.
Static typing, code analysis, automated testing, etc. are all great tools that become counterproductive past a certain point. Where that point is highly depends on what you're doing and typescript is one of the most flexible type systems at letting you make that choice. I'd say a lot of people are terrible at recognizing when they went too far with it for no practical gain.
My only problem with it is that they can't fix the shit JS semantics.
I am professionally familiar with typescript and explicitly not making a concrete argument for or against it right now.
I'm just pointing out that having an opinion about typescript is not the same thing as having an opinion about types. Something I think people are (intentionally?) conflating in these comments.
I think it’s just a path a lot of developers have traveled, starting with JavaScript and moving to TypeScript. If you’ve gone through that pipeline than you probably feel very strongly about the benefits that TypeScript provides and it’s the fulcrum for your opinion on static vs dynamic typing
Too many web developers, basically, I don’t think the conflation is intentional
I think you have to give typescript a bit of a pass in terms of types. MS had unique constraints when designing it originally, so it's never going to meet the expectations of more hardcore "typists", but I think they've done a great job given the unique challenges. Not perfect for sure, but it's one of the few newer technologies where I think it's a clear win.
Typescript's typing system is great and (for me) makes frontend development tolerable. It's annoying that it's optional. tsconfig and the related ecosystem is a disaster.
I still have hopes that Kotlinjs can fix the distribution size and the tooling around binding to js libraries.
This applies to much more than static typing as well. Anything built, can be built well or it can be built quickly.
Although I often have to make trade-offs for immediate gain at work, there is a big difference between the quality of my work over time vs the quality of other devs who default to immediate returns. I will say in their defense, they play a role in the team. But I wouldn't want to work on a team where avoiding upfront costs was the expectation rather than the exception.
And I definitely think less of you as you as a developer as you openly admit to dogmatic and myopic prejudice regarding the nuances of typing systems and the complexity of software development contexts.
Interesting. Would you have had the same response if GP stated:
"I've worked with my fair share of vanilla JS devs who refuse to acknowledge the benefit of testing. Most of them bemoan the upfront cost of testing everything without realizing the benefits of it. I absolutely think less of them as developers."
Again another binary dichotomy that doesn't neatly fit reality. I imagine the number of developers that would unilaterally dismiss all testing practices is very small.
I mean, I'm not going to say it to someone's face but I'll definitely be judging them. Most of those who have pushed back against typescript have done so for similarly dogmatic reasons or even straight ignorance (I dont want to learn something new), so I'm not sure who is really the winner or loser in this situation.
I absolutely realize the benefits of it, and I also recognize it doesn't cure all. Everything has trade-offs. There were plenty of amazingly complex and large projects written in vanilla JS before typescript ever existed, and somehow they aren't falling apart and crippled. It really depends on the developers involved more than the language or the dogma.
This sounds like its just Typescript evangelizing. But while I love types, Typescript kind of sucks and sucks the fun out of writing Javascript. If I am going to add a compilation step to get types, I would rather write Scala or F# and compile to JS, then you also get rid of all of the warts of JS that Typescript doesn't get rid of.
Similarly, choosing between 'int' and 'long' isn't part of a type system, unless the only semantics your data has is being an integer of a given width. Types are semantic, and telling me "height is 32 bits" tells me jack shit about units of measure or anything else.
Strong static typing is useful in the environment where the code is not adequately tested. That's because tests adequate to make the code bulletproof will also detect the problems strong static typing could detect, rendering SST superfluous.
So, how often is poorly tested code out there? From the popularity of strong static typing, it must be ubiquitous.
The point of static typing is that the compiler does checks before your program even starts. And that avoids to write lot of coverage tests that are needed (but usually just ignored) with languages that massively use loosely typed data containers (variables).
Sure, if you're going to be in an environment where the code is going to be bad because of inadequate testing (and I admit this is quite common, due to the cost of the testing needed), static typing lets it be somewhat less bad. It's just important to understand it's a band-aid, not a panacea.
Static typing, as done in mainstream languages, doesn't show your program is correct. Is you compiler written in a mainstream language correct just because it compiles? Of course not. Don't point to academic curiosities with dependent types if they aren't actually used. Extensive testing, on the other hand, is used in the "real world" for assurance.
Every code out there on the real world is full of baindaids and duct tape. Yet, the actual amount varies a lot, as does the centrality of the patched things.
Strong types aren't just a correctness guarantee, they also help to discover structure and interfaces that previously were implicit.
If a developer can jump into an unknown part of a codebase and quickly see that following a certain structure will automatically make their code work for them without needing to read all the code first and double checking if it's just a random convention versus a strict interface so they don't reinvent the wheel or build code that doesn't fit in with the existing structure, then that's worth a lot and something you cannot simply cover with tests.
Integration tests are different, but I tend to believe that a strong enough type system should be able to negate the need for unit tests completely.
If anyone can think of something a unit test could test for that an arbitrarily* complex/strong type system couldn't I'd be interested to hear it. It's possible, I just can't think of any.
When I say adequately tested, I mean much more than unit tests. Unit tests aren't going to give you bulletproof code. They don't detect bugs that arise from interactions at a higher level.
An arbitrarily complex type system, with dependent types, could detect any bug, since it's formally equivalent to requiring the program be proved correct. But using such a type system is so onerous that I don't know anyone who realistically does it in production.
If you wrote such types, you're basically giving a formal specification of what the program is supposed to do. That could then be used, and likely much more easily, for high volume property-based testing.
Unit tests and types solve somewhat different problems. You can force a unit test to check everything a type system does with effort (mostly by throwing wrong types at your API and verifying you get an error), but I'm not sure how you would use the type system to enforce sort() actually sorts. Sure sort can take a listOfFoo and return a sortedListOfFoo - but that doesn't verify sortedListOfFoo is actually sorted - and in many cases I want an API that is find with listOfFoo but should handle sortedListOfFoo as well.
With dependent types you can encode any proposition in first-order logic (?). You can indeed define an array type that is necessarily sorted. It won't be pretty but it's possible.
I like type systems as much as anyone but I can't see how your type system will check that sin(pi) == 0
if you have a type system expressive enough to do things like that, you've basically got another turing-complete layer on top of your existing language, which is itself ... dynamically typed.
High test coverage is useful in the environment where the code is not adequately typed. That's because static types adequate to make the code bulletproof will also detect the problems unit tests could detect, rendering high unit test coverage superfluous.
So, how often is poorly typed code out there? From the popularity of test coverage tools, it must be ubiquitous.
The turned-around argument has a problem: testing should reveal type bugs, but static typing (of the kind typically discussed for mainstream languages) can't reveal non-type bugs.
In a situation where extreme levels of testing occurs, the extra assurance from strong typing is minimal. In a situation where strong typing is enforced, extra testing is still very useful.
I don't think it's possible to test a sufficiently complex system that isn't somewhat restricted in the states it goes through, which requires typing.
Consider a finite state machine with N states. There are a priori NxN possible transitions. In many cases however most of them are impossible, and you really have only O(N) feasible transitions.
Sure if you make O(N^2) tests you don't need typing... but typing could have made many of those transitions literally impossible. You don't need to test the impossible. For a sufficiently large codebase, you want to limit as much as possible the amount of tests you need.
Also I have more than once identified issues in our testing framework because when I made stronger types I uncovered bugs that escaped our tests. Non-deterministic concurrency bugs are extremely hard to test for. So yes, testing can uncover type bugs, but typing can uncover untestable bugs.
> Strong static typing is useful in the environment where the code is not adequately tested. That's because tests adequate to make the code bulletproof will also detect the problems strong static typing could detect, rendering SST superfluous.
Ok, but it's cheaper to not have to write those tests than it is to write those tests.
Yes. It's cheaper to have a lower assurance your program is correct than to have a higher assurance. The claim I'm making is that at the high end, the extra assurance from strong static typing becomes minimal.
Good tests only render SST superfluous insofar as the person writing the tests is capable of never making a mistake. The thing about good static type systems is they don't typically make mistakes in the domain they work in. The fact that you don't have to muddy your test with a bunch of type checking logic and instead focus on the thing actually being tested is just the cherry on top.
That is a very original take, but I think you have it reversed. Typing removes the possibility of _some_ errors. Those are tests you do not _need_ to write (you can still write them if you want), thus having more time and attention to write tests that matter. Typing is a baseline, and anything over that baseline you can deal with with tests.
It is like the things you already trust when you code. Let's say, that the file system works, that the network stack of your OS works, or that the cpu works. You can rely on them to use your time for the things that matter. This is a much better model than simply testing everything yourself since, as the tests number increase, any change to the codebase involves more and more test changes.
> Strong static typing is useful in the environment where the code is not adequately tested. That's because tests adequate to make the code bulletproof will also detect the problems strong static typing could detect, rendering SST superfluous.
Your logic is the same as follows: seat belts are useful in an environment where drivers are not driving adequately. That's because adequate drivers will drive in a way to avoid any danger that the seat belt would prevent, rendering seat belts superfluous.
Ultimately, the problem is adequate drivers (as in described above) or bulletproof tests do not exist, they are just a concept. A test can prove the presence of an error, not the lack of errors, and the argument only works in absolutes.
You seem to be thinking that testing for type errors is something additional to general testing. It's not. With sufficiently strong logic testing, the testing for type errors comes along for free.
The analogy with driving doesn't make much sense. After all, accidents sometimes happen without the driver being to blame, and the marginal cost of putting on a seat belt is very low.
No. It is useful in almost every environment other than things like REPL. I can write TypeScript for hours without writing any tests, run it and fix a few minor logic bugs and code is production level, which is great for prototyping. No chance with plain JavaScript without typing -- it would be at least a day or two and I would constantly run into stupid typos or other errors that can only be found at runtime.
Yeah, big piles of untyped Django spaghetti need a behemoth suite of integration tests in order to remotely approach the sort of runtime guarantees that typed languages give you for free.
When you use a typed language, you can use your integration tests to actually verify business logic, exception handling, etc. instead of having to write a dozen test cases to make sure that doThing(table, index,*kwargs) doesn't blow up when 'table' is a list or 'index' is bytes...
If you adequately test your code for logic bugs, you test the type correctness for free. So, yeah, writing those logic tests is hard. But at least they are finding bugs that static type checking never will (absent the unrealistic scenario of proving your program correct via a type system.)
(It won't find latent bugs that can't currently be exercised, so the testing can't be one and done.)
you can't ensure that you hit every possible case in your test suite. type errors can still slip through in production, just like logic bugs do. it happens all the time in real-life code. that "adequately" is a no-true-scotsman.
if you use a static type system you can guarantee there will be no type errors at runtime. why on earth wouldn't you choose that? you can still write logic tests!
and when you leverage the type system to make illegal states unrepresentable, you can make certain classes of logic error impossible as well. some of your tests become tautologies and you can delete them.
You are assuming type errors are caught by tests explicitly written to detect them, as opposed to being found by other kinds of tests. For example, property based tests, with automated generation of inputs to a program or module, don't explicitly test for type errors, but are still (in my experience) quite good at hunting them out.
What testing cannot find are latent errors not exercised by the program. Do I care about these, though? Arguably these would be found by testing internal interfaces and elimination of code not reachable by tests.
The general argument I am trying to make is that the marginal value obtained by strong static typing declines as testing increases, and that in the limit goes to zero. If a program is adequately tested, is it still worth doing? This is not clear to me, and the arguments given here have not convincingly demonstrated that it is worth it.
Also: if you find yourself in a situation where strong static typing seems useful, you should be alarmed. It means you aren't testing your code very thoroughly.
>You are assuming type errors are caught by tests explicitly written to detect them, as opposed to being found by other kinds of tests.
I am not assuming that.
honest question: what language do you have in mind when you think "static types"? your perspective is so starkly different to mine, you seem to be operating under totally different assumptions about what types can and can't do.
I mean this sentence:
>Also: if you find yourself in a situation where strong static typing seems useful, you should be alarmed. It means you aren't testing your code very thoroughly.
is just baffling to me. it's so self-evidently absurd that I can't even argue against it. what is there even to say?
have you even used a modern statically typed language, with type inference, generics, null safety, algebraic data types, pattern matching, etc? I cannot imagine trying to maintain a big codebase without them. they don't slow me down, they speed me up. they aren't just about catching trivial int-instead-of-string bugs, they are are core tool for modelling data and business logic. they let me define problems out of existence (see e.g. this series of posts https://fsharpforfunandprofit.com/posts/designing-with-types... for an introduction).
and then there's rust and newer-generation languages with borrow checkers and affine/linear types, ruling out entire classes of memory and concurrency bugs ... your test suite cannot rule out data races, but rustc sure can.
you are making the same arguments people were making in the 2000s when most static languages sucked because they didn't have these features. I don't want to go back to a time before sum types.
>What testing cannot find are latent errors not exercised by the program. Do I care about these, though?
you should, because in production your program must endure orders of magnitude more variety in inputs, uptime, and runtime conditions than the test suite can exercise. it can and will get into weird states you didn't anticipate. yes, you can fuzz, yes you can property test, I know all about that. those things are good. but I don't get why you wouldn't also use a static type system to provably rule out classes of problem across all possible code paths. why settle for less?
"you should, because in production your program must endure orders of magnitude more variety in inputs, uptime, and runtime conditions than the test suite can exercise. it can and will get into weird states you didn't anticipate. yes, you can fuzz, yes you can property test, I know all about that. those things are good. but I don't get why you wouldn't also use a static type system to provably rule out classes of problem across all possible code paths. why settle for less?"
Because type errors in unit tested code are basically non existent. It's a fictional problem and as such there is no point in spending real resources chasing fictional problems.
There is plenty of research that shows that static typing gives no benefits (statistically insignificant) when it comes to software correctness.
What you are asking is why shouldn't the local government spend money on a Yeti patrol to protect the general public against Yeti's? The Yeti patrol will eliminate an entire class of problem (Yeti attacks).
>Because type errors in unit tested code are basically non existent.
this is short-sighted. I am not talking about trivial string-instead-of-an-integer errors here. powerful type systems let you encode far more sophisticated constraints on the program. safe rust makes data races into a compile-time error via its type system, for example. unit tests can't do that.
I think you're greatly underestimating what types can do for you. you seem to have this mental model where you would write the exact same kind code with a type checker as without one, and the only difference is whether you have to convince some pedantic bureuacrat that your code is correct when you already know it is.
but when you have a powerful type system, you don't write the same kind of code. the type systems helps drive design, similar to how tests can drive design. you have probably seen code that is bad because it wasn't written with testing in mind. there wouldn't be much value in adding unit tests to the code right away -- you probably need to do some highly invasive re-architecting to make it testable.
so, is it really such a stretch of the imagination that code can also be deficient because it's untypeable? perhaps the reason you don't see much benefit to types is because you didn't write the code with types in mind, as a design tool.
there is a learning curve to writing testable code. the same is true for types.
Sadly, it still took 4 days to clear the deadlocks out of my Rust program.
No, my mental model is removing the type checker allows me to write shorter, more concise and higher quality code. Dynamic typing enables much better code styles than static typing.
> the type systems helps drive design
Ahh you are an complexity merchant, if only I made my code more complicated all my problems would be solved. I'm afraid not, the more complicated your code the worse it is.
No I'm afraid, I have actual real commercial experience of doing both styles of software development, the dynamically typed code is the better approach by far.
It's not that I don't understand you, it's just what you are saying is a load of rubbish. :p
I don't consider it productive to be personal about technical topics. It's best to divorce yourself and the other party from the issue to limit as much bias as possible. You don't need to worry about the good-faithness of an objective, egalitarian discussion. It's never truly objective, but you can at least catch yourself saying things like "I might think less of you as a developer" and recognize that's a preconceived notion. In some cases, it may be turn out to be accurate, but it's definitely not always. Dynamically typed languages have had an important role historically in software. We should not categorically denounce people who prefer it as lesser developers. I also generally prefer static types.
I explicitly said "as a developer" because it's not a personal judgment. You can be a good person and also have a terrible take as a software engineer.
> We should not categorically denounce people who prefer it as lesser developers
I don't think you've justified this point. I'm comfortable with my position on this.
What you say is true, but it’s also true that the person you are replying specifically made their remarks about productivity - you’re both talking about what is best at work.
It seems reasonable to argue that statements that you won’t even discuss X any more and would rather judge the person as less competent professionally are unproductive. Not saying I agree, btw. But it does seem like a pretty basic point. One of you is talking about preserving your sanity and the other about output. It’s not necessarily a disagreement even.
I still think it's reasonable to argue that's not a "productive" approach, but like I said, that's not my own opinion, I just think it's a reasonable argument.
I don't necessarily think it makes someone a "lesser developer" if they prefer untyped languages, but I DO think they've either had to maintain anything long term or it stayed relatively small.
Whether that makes them lesser or not isn't really for me to say, but I can say I'm definitely on board with the idea that types increase productivity the longer a system is maintained. Unless used poorly, types don't automatically mean you use them well, but they make it a hell of a lot easier to do the right thing.
Again, I don't mean "personal" in the sense that you are making a statement about someone's worth as a person. It's "personal" because you extrapolate information about an individual person's technical skills from a single opinion than you have any real factual justification to do so. People will always find ways to defy your preconceived notions.
Some prejudices are rational. To paraphrase, it would take an enormous amount of prior trust and respect before I even humour someone trying me to convince me the sky is green with a straight face.
Some issues are settled enough that there is no need to discus them any further. It is okay to automatically mark people on the wrong side of such issues… let’s say ill-informed.
Static typing, I believe, is close to being one of those issues.
I think this is OK as long as you keep your mind open to situations that you have not considered.
Imagine someone is working in a relatively niche new programming language ecosystem which is dynamically typed, allowing the language to have some richness that modern type systems don't support. I don't have an example because I don't know of any such language in 2023... BUT back in the 70s and 80s this would have been Lisp. Lisp couldn't have been strictly typed back then because, AFAICT, type systems hadn't advanced enough to express the sort of metaprogramming that made Lisp unique and awesome. This was at a time when most popular languages were strictly typed.
I would hope that you would keep your mind open to whatever that maps to in the 2020s.
Now, if someone says "I like JS over TS because types are annoying and slow me down", then yeah, I don't have much patience for that either.
You make points that seem great to me although I know almost nothing about Lisp or 20th century programming.
The thing is, I've yet to encounter a single instance of such an argument today. Every single time it ends up being "I like JS over TS because types are annoying and slow me down". It hadn't even occurred to me that laziness and sloppiness weren't the the only reasons to write in dynamically typed language.
I suppose what I'm saying is I'm quite interested in seeing what kind of evolution some dynamically typed language could offer in the future. Although with no signs of its coming, I'm going to stick to TS because it's objectively better for anything but very small projects.
TS is an interesting example to pick when trying to argue that types are better. You have all the same downsides people pick on when arguing against strong, static typing, but since the type system isn't sound you still can't rely on the input to a function being the type you expect (and the problem is worse anywhere you inevitably interact at least a little with the JS world outside your TS bubble).
At best it's a form of documentation that enables some simple linting rules and a bit of jump-to-definition magic. Like, that's a benefit (mostly -- I tend to think that type signatures implying more guarantees than they provide is a recipe for inadvertently relying on falsehoods), but it's not as clear-cut of a win as you see in other statics/dynamic tradeoffs.
> The thing is, I've yet to encounter a single instance of such an argument today.
I don't mean to be unnecessarily contentious, but isn't that the point of undiscovered territory?
We haven't encountered it yet, when we do then as awareness grows that pattern, idiom, theorem or concept will be picked up by mainstream statically typed languages.
It's enough to believe that the properties of (for example) JavaScript might lead to an as yet undiscovered pattern that is not possible in current statically typed languages.
Yes sure, it's always possible. My narrow experience leads me to believe, however, that some necessary threshold hasn't been crossed yet. And there's vanishingly little chance that I personally will be participating in such a revolution. Therefore from my perspective, it isn't happening yet and I can only wait to see if it does - meanwhile, I'm on the opposite side.
Python fits that niche now. Half the way tensorflow, et al work is by doing brain surgery on the AST in a way that resembles hey day Lisp's 'even the code is just s-expressions, process it as much as you want to the point of 80/20ing your way to your own compiler'.
Imagine working on a networked system that uses types to make it impossible to express operations that violate data security and integrity constraints, and where such constraints control whether data is allowed to be read or written from or to a given endpoint. Imagine further that the types governing permission to read or write depend on environmental state that can change at any time--for example, as soon as a specific person changes roles, the set of ports they are allowed to read and write changes.
The type system enforces those permissions: writing to an impermissible destination is a type error. The types applicable to an entity are not necessarily knowable at compile time; some of them might change at any time.
I had a job working on such a system. It supported a type system implemented in Haskell with both static and dynamic type disciplines, where values were tagged with base types designed to be checked dynamically in hardware.
Was the programming language dynamically typed? Yes. Was the programming language statically typed? Yes.
It sounds like you have an implicit prejudice against those who do not "keep [their] mind open to situations that [they] have not considered", since the way you phrased your concern is moral in nature. You see it as morally bad not to have an open mind in that way.
Yeah, sure, I do in fact think it is virtuous to be aware that we don't always have complete information and that we should be willing to revise our opinions when new information is encountered. Is that controversial?
No, no, not controversial. I intended to make it easier to understand the point by demonstrating for the more literal-minded that using moral language sets up two halves of a dichotomy, one preferred to the other, so that such prejudices needn't be personal but rather merely moral. The difference is of course that which side of a moral dichotomy is a choice that can be reversed (e.g. making an effort to open the mind) while personal matters are immutable in those people.
If your prejudice prevents you from having a conversation with Matz or Jose Valim or Van Rossom or the creators of Julia about software development, you may want to reconsider your prejudice as a hard and fast rule.
You can't honestly believe no one who likes to develop in a dynamically typed language has nothing interesting to say about software development?
Doesn't everyone have those? Like, I'm unlikely to really engage with someone about my health unless they're a doctor or otherwise have some area of expertise. Or if it's just a casual, friendly conversation, to kill some time.
Everyone has biases. But you aren't describing a bias, you are describing a refusal to engage with people below you. Speak for yourself, I don't have that.
We’re all humans and life is very complicated with many facets. If we all looked hard enough I’m sure we could find a person you’d refuse to engage in a debate with over their “thing.”
But that wouldn’t be a wise use of time in my opinion. And that’s what they’re ultimately driving at: we all have limited time to expend, so do it in things that matter to you. I believe “matters to you” is a bias.
"Below you" is a bit of an exaggeration, I think. I said that I might think less of someone as a software engineer if they express an opinion that I think is particularly bad. Hardly egregious, in my opinion. I also might not - it frankly depends a lot on the situation.
These people opinions are based on bad information.
Beliefs inform decisions and other beliefs
I disagree with them on a huge number of fundamental things in software. Most of their fundaments are claims with no evidence. Due to that, I simply do not care about what they have to say most of the time.
I can't (or, more accurately, don't) believe that, but I can certainly believe they have nothing interesting to say about the topic of static vs dynamic typing.
An open mind is like a fortress with its gates unbarred and unguarded.
It is both useful and allowed to have a certain level of belief that won’t be crossed without heavy effort. The OP didn’t even say they were closed to the conversation completely, but that a random person with no pre built trust isn’t going to get the time of day from them to rehash the same settled argument.
Python got type hints bolted on. A type system for Elixir is being worked on. Dynamic typing was a prerequisite for Erlang to enable hot-code-reloading. Julia is can be typed for an extra speedup. Stripe built a type checker for Ruby called Sorbet.
I'd love a dynamically typed scripting language with the following two properties: no "import" mechanism to split code over multiple files, and no way to execute more than (say) 256 lines of at most 128 bytes each in the file.
Dynamic types are nice for quick & short scripts, and actively detrimental for anything long and complex. Why not enforce that? As soon as it gets so long it doesn't run you know it's time to rewrite in a statically typed language. Instead all existing scripting languages allow unlimited growth in code size, making it easy for programs to grow beyond the point where the language is useful.
> Dynamic types are nice for quick & short scripts
A long list of big products and companies are there to disagree with this. I am not saying that dynamic is better or worse. But saying that dynamic languages are only good for quick short scripts is a wrong generalization that has not one exception but many exceptions.
> To paraphrase, it would take an enormous amount of prior trust and respect before I even humour someone trying me to convince me the sky is green with a straight face.
But people regularly say the sky is blue, and it's clear as day that it's not when the earth has turned and your side isn't facing the sun.
I don't think that's a statement that most would agree with. What is the sky? Sure the atmosphere can be called blue, but the sky is what's visually above the earth. At night, that's black space. The atmosphere is mostly transparent at night.
The sky isn’t inherently blue. It’s essentially colorless.
Only due to Rayleigh scattering do we perceive it as blue, but that’s not due to the absorption and reflection of different wavelengths we associate with innate color. Note that the color changes depending on the angle of the sun, even to the point that it’s purple and red at a few times during the day.
> To paraphrase, it would take an enormous amount of prior trust and respect before I even humour someone trying me to convince me the sky is green with a straight face.
Don't venture into tornado country; your doubt may be your downfall.
As someone who usually prefers JavaScript over TypeScript but has used many typed languages over the years and generally finds then easier to work with in a way, I agree that one should not pre-judge based on something like that.
But he's just being honest. Many developers have been making that judgement for many months or even years.
Nah, someone opposed to typing is either a shit developer and/or a madman who codes too clever by half mad shenanigans that take advantage of the lack of strong typing. I wouldn't want to have to maintain or depend on code written by either.
For the very rare cases where a lack of strong typing is needed, most languages with strong typing offer ways to handle that (i.e. the Object and Dynamic types in C#)
I think you're being a bit too sensitive, and it's not personal. Someone is espousing a (bad) technical opinion about their field of work, it's not unreasonable to say you respect them less in their field of work. That's not personal.
It's like if you met a builder who refused to use a hammer and insisted on bashing nails in with the back of their drill. It's not "personal" to say you'd respect that person less as a builder, regardless of how much you'd enjoy having a drink with them.
It is "personal" if you attach someone's technical opinion to broader implications about their own competence. If you disagree with a technical opinion, say so and move on, there is no reason to even discuss someone's own personal skillset, experience, or value as a developer. It's a silly fallacy to automatically label people who disagree with you as incompetent. All it does is foster bias and stifle actual discussion. The responses to this post are evidence that this is not as cut and dry as the original posts suggests, so I suggest we try our best not to cover our ears and embrace tribalism just because we think less of someone's opinion. I don't think I'm being oversensitive by saying this is unproductive dogmatism. It is my honest opinion, yet I do not extrapolate to mean anything about the proponents' value as a developer. Unconscious bias is a pervasive problem for everyone, especially when it comes to binary holy wars like static vs. dynamic types. This is more akin to a builder who uses a nail gun rather than a hammer. Hammer enthusiasts can either acknowledge that both approaches have tradeoffs or they can petulantly insist that people who don't use their methods aren't "real" builders.
I'm sorry but this is a discussion about their competence. You can't separate someone's opinions on technical topics from their competence in the very technical field you're discussing. If a cartographer has the "opinion" that the world is flat then that directly speaks to their competence as a cartographer.
For starters, "the world is flat" is falsifiable and trivially disproven with evidence. "Dynamic types are better" is neither of these. If you're going to pin someone's professional value to a single technical opinion, you should at least be able to back it up with data.
I'm currently responsible for a very large system built in raw javascript where function definitions like this one in the article:
function birthdayGreeting1(...params)
...are the religion.
It's awful. I hold the people responsible in very little regard.
> At this point it's beyond a hill I'm willing do die on. I'm not really interested in discussing it. If you don't get it I probably don't want to talk to you about it, I might even think less of you as a software developer.
That's a pretty strong take! I don't think there are many things that I feel similarly about... maybe if someone suggested that they don't need test environments and can just deploy changes to prod without testing or CI/CD and just see what happens, when it'd be my employment on the line, but even that's a pretty contrived and out there example.
> The amount of pre-existing respect for someone I'd need to have before I engage in a good-faith discussion on "are types good" is pretty high.
My problem is that not all type systems and the way you use them are equal.
When working with back end code, I really like .NET or even Java having a type system there for me. I know that people suggest that they have their own shortcomings (type erasure, NPEs, no multiple inheritance, even smaller things like C# enums not supporting methods) and that there are better options out there, but generally you can turn off the part of your brain that'd worry about the language too much and just deal with the domain problem at hand. Something like JetBrains are also excellent, because with the type system suddenly the tool also can reason about the language constructs you're using and give you all sorts of good suggestions and refactoring options.
Whereas with something like TypeScript in combination with React, there are times where you fight the type system instead. That's just the impression that I got working on a few projects for a while, in comparison to React with JS (perhaps the code was also a bit too clever), while with Angular it felt more coherent to me (despite Angular being more complex otherwise and not really my first choice). In the end, I gravitate towards Vue with JS for my personal stuff, but it's not like you can just say no to TypeScript when you need to maintain something long term.
I can't actually remember who said that they ditched TypeScript for similar reasons, but the argument was basically that a non-insignificant part of their codebase was there just to satisfy the type system. TypeScript does what it's supposed to... but it feels like it could be easier.
> Some type systems suck so badly that I can see why people would be tempted to believe that all type systems suck.
But that's my point: people say that exact thing about Java and .NET, while I find them usable. Meanwhile TypeScript has cool stuff like union types and other stuff to the point where you can get pretty clever with it (https://codegolf.stackexchange.com/questions/237784/tips-for...), which many would describe as the type system being objectively better, yet it's also more difficult for me to use.
In my mind, a good type system would let you do both basic stuff easily without too much work (to make sure that refactoring doesn't make you shoot yourself in the foot) and also encourage you to write the simplest code that you can get away with, while allowing you to get clever in the select few places where that is actually needed.
Which is funny, because adjacent to that, Java and .NET (web) frameworks can be a masterclass in incidental complexity, even though for me the type systems don't get in the way too much.
Edit: actually, I think I'll migrate a JS project to TS, this time in Vue. Perhaps Vue 3 will be a pleasant experience and if it won't, then I'll have a concrete list of things that caused me to feel this way.
I'm pretty agnostic about the whole thing. The topic has been done to death already. Types are great, but some ways of handling types can get in the way sometimes, and the excessive type declarations in older Java was just painful. Dynamic types can work fine too, but they work better in smaller projects than in bigger ones. Types do give you a lot more to hold onto, even if they're only compile-time types like Typescript. In large, complex projects where you're juggling a lot of different types, you want to know what you've got in your hands.
That's pretty much my take. I don't hate anyone for having different ideas about it. But if you're going to use Typescript, don't use 'any'. At all. Unless you really don't know and the next step is figuring out what type you've got.
>maybe if someone suggested that they don't need test environments and can just deploy changes to prod without testing or CI/CD and just see what happens
That's actually not hard, you need to be able to run in dry mode, and run the same in an existing instance (in parallel), then compare the results. If you are happy you can continue with the roll up, disabling the dry mode.
Also on the “types good” side of the fence. HOWEVER, I also believe that mainstream typed languages cannot represent some situations very well. Often the only attempts to do so are in some weird Haskell extension and it feels rather… inelegant. Bottom line: types are better; but our current type-systems are not the final word on types.
This is basically where I am at. Most libraries and applications are trivially typed even in languages with relatively poor type systems like Go and Java. The necessity, or even benefit, of weak and/or dynamic typing is extremely rare and most languages have workarounds or escape hatches for those exceedingly rare cases.
Dynamic typing also makes a ton of optimizations basically impossible and even after monumental efforts languages like Javascript are still quite slow, inconsistent and memory inefficient outside of trivial benchmarks.
> Javascript are still quite slow, inconsistent and memory inefficient outside of trivial benchmarks.
I thought this meme was dead already. Of course, you might not be able to squeeze out the same amount of performance compared to a brilliantly written C or Rust program, but for what it is, JavaScript is pretty damn fast already.
DOM manipulation on the other hand, is still a very common bottleneck people come across when writing typical JavaScript code.
> but for what it is, JavaScript is pretty damn fast already.
It's fast compared to other dynamically typed language implementations but it's still very slow compared to basically all of the popular statically typed languages.
Trivial benchmarks where all of the significant data structures and networking are done in C++ and the tiny amount of Javascript is just passing some strings around. I guess it's "fast" if you can restrict yourself to little more than hello world where you do nothing more than pass a few strings to functions written in faster languages.
Notice that in the Java, C#, Go, Rust, Swift or Ocaml benchmarks almost all of the underlying data structures and much of the networking stack are built in the respective language. This is not possible with Javascript, Python, Ruby etc. because it would be ludicrously slow and extremely memory inefficient.
> It's fast compared to other dynamically typed language implementations
true
> it's still very slow compared to basically all of the popular statically typed languages.
Not true.
The main slowdown for javascript (AFAIK) is the checks the optimizer has to put into place to ensure the assumptions it's made about the type are still valid. If, however, those assumptions are valid then javascript ends up emitting pretty much the same assembly that you'd see for and highly optimized statically typed language. In fact, there are some circumstances where it can beat a language like C++ or Rust due to the fact that it has to incorporate runtime information into optimizations.
With C++ or rust, if you add dynamic dispatch, unless you are doing PGO and whole program optimization, you are pretty much sunk with 2 memory lookups on every function call. This is the case where javascript can end up beating C++/Rust.
(All of this is talking about hot code after warmup. During the initial execution javascript will almost certainly always be slower).
Part of the proof of this was asm.js, the precursor to wasm. V8 at the time it was introduced could execute asm.js nearly as fast as what firefox could do with it's optimized asm.js compiler. That is, when you stripe out all the actions that make javascript slow, it very often ends up being just as fast as a compiled language.
What stuff ends up making it slow? Generally speaking, stuff that makes the types unpredictable (adding fields, removing fields, sending in a number and a string and expecting the VM to be able to handle both).
You can see a lot of this writeup around the discussions about why Dart was originally "optionally typed". Basically, the entire selling point to make dart fast was simply to remove the abilities to dynamically change types like you have in javascript. With that, the VM authors at the time were capable of making a VM that's every bit as fast as what Java has.
> With C++ or rust, if you add dynamic dispatch, unless you are doing PGO and whole program optimization, you are pretty much sunk with 2 memory lookups on every function call. This is the case where javascript can end up beating C++/Rust.
GCC at least is capable of speculative devirtualization by using local heuristics, without PGO. And of course it is capable of devirtualizing in many cases when the knowledge actual type can be constant-propagated.
Also note that the vast majority of calls are not dynamic in C++ (as opposed to most dynamic languages), so devirtualization is significantly less impactful.
Fair point, AFAIK C++ templates are pretty heavily used to avoid a dynamic dispatch. Couple that with the fact that polymorphic OO has fallen out of favor generally and I could see this mattering a lot less now-a-days.
> That is, when you stripe out all the actions that make javascript slow, it very often ends up being just as fast as a compiled language.
ok...
> In fact, there are some circumstances where it can beat a language like C++ or Rust due to the fact that it has to incorporate runtime information into optimizations.
People used to make the same claim about Java and in every single example I've ever seen the Java/Javascript is extremely optimized, performance isn't consistent across VM versions, and the C++/Rust is extremely naive (usually allocating unnecessarily and not using arenas in hot paths that are allocation heavy).
None of these tests really show JS winning, but they are showing that it's within spitting distance of C++ in multiple tests [1]
And, you can look at the code yourself, most of the examples read pretty much exactly the same as their C++ counterparts.
Mind you, this is also a test that looks at execution start to finish and doesn't give warmup time (which will always favor statically compiled languages).
> performance isn't consistent across VM versions
That's true for C++ compilers, so why would you expect performance to remain constant with a JIT compiler?
I think what cracks me up about this conversation is that it's an almost verbatim repeat of a conversation I had on reddit a few weeks ago.
There's a certain segment of the developer population that I don't think realizes just how fast C and C++ are. Javascript is _relatively_ fast when compared to other dynamic languages, but not when compared to C, C++, FORTRAN, etc.
The take I have is that with a dynamically typed language you still have a static analysis step. It’s just that the analysis happens in your and other developers’ brain and it is objectively worse. I honestly can’t think of a single thing in favor of dynamic typing.
I write Clojure in my day job and it’s insane how often we have issues where it would have been immediately caught by a static type check.
I'd love to learn more. Are you part of a large team (enterprise or SMB, whatever you can share)?
How have you experienced using Type Clojure, spec, Malli, etc. to determine correctness?
I've only worked on solo projects with Clojure, with most of it fitting into my head. I imagine with teams of size N > 1 things can change quite a bit.
Can you describe your usecase for which JavaScript is slow? There are many languages that are slower than js like python or elixir, but they are doing just fine, that's why I won't agree that js is slow, but sure there are cases for which js just wasn't designed, and any CPU intensive task will be slow, but there are ways to get around it as well.
> Dynamic typing also makes a ton of optimizations basically impossible and even after monumental efforts languages like Javascript are still quite slow, inconsistent and memory inefficient outside of trivial benchmarks.
Please provide evidence for this extreme claim. I can point to benchmarks[1] where JavaScript is competitive with or even better than compiled static-typed languages like Java.
(any argument that those are "trivial benchmarks" is automatically invalid without actual empirical evidence)
There's a temporal component to the argument. Strong typing in the 1990s wasn't very good. Dogma at the time resulted in very rigid designs. The verbosity of it all was staggering. Incorrectly-used Hungarian notation ran rampant and made everything hard to read while still not helping anything [1]. The costs were a lot higher than it is today and the benefits a lot less.
Nowadays, the static typing is much nicer, with both higher benefits and lower costs, and it makes the costs/benefits analysis much more likely to come out in favor of static types.
In the late 1990s when I was cutting my teeth, I did a lot of Python, and I was almost 100% dynamic language until ~2015. In hindsight, I might do the same again even if thrust back in time. There just isn't a great static option back then. (I'm not saying 2015 is the year it became practical, I'm saying that's when I finally moved into static languages. 2010-2015 or so I was in Erlang, doing things that most other languages couldn't do at the time, so that forced me into a dynamic language. C# was looking pretty good in that time frame too, it just wouldn't have run the systems I had on anything like the resources I had at the time. It could probably easily do it in 2023 though.)
Now I even prototype in static systems, and it's a better experience than prototyping in Python was. Like, by quite a lot, honestly. In the end, I don't find it that much of an impediment to make sure that if I want to call a method on a thing, that the method actually exists.
Dynamic typing will never disappear; there's a certain small size of task for which it'll always be more advantageous than static typing, and while said tasks may be small, there's a lot more of them than there are large tasks, so it's a completely valid and sizable niche. But I do think over the next 10-20 years we're going to see the "scripting" languages return back to "scripting" and away from "systems".
I think that in the end, the dynamic scripting languages being used for large tasks will be seen as a reaction to a misdiagnosis of the problems in the 1990s. The code was atrocious in the 1990s not because it was statically typed, and therefore the solution is to go dynamically typed. The code was atrocious in the 1990s because it was poorly statically typed, and the solution was to get better. That said, "getting better" did take a long time, and for many legitimate reasons.
(Much ink is spilled on the so-called rapid pace of technological innovation in our industry, but programming languages move on decadal scales. Programming languages still have only barely grappled with a multicore world, and haven't grappled with a heterogenous computing world at all (GPUs on one end, efficiency cores on the other). Things are not always in as much motion as we fancy.)
[1]: Particularly, Hungarian notation is supposed to supplement the type, not just reiterate it. If you're in a language where you can't easily declare "a width is an int", then Hungarian notation suggests calling a width variable something like "wdthDialog", so that you stand a chance of noticing that you passed a "hghtDialog" in the wrong place. But the way it was used a lot of the time is you got "u16Width" instead, where u16 meant unsigned 16 bit int... but that's already in the type. Using it that way just adds an extra layer of hierglyphicness to the already ugly code. One of the several innovations that made static typing languages more feasible is that in most languages designed in the last couple of decades, you can declare something like this with something like "type Width int", and then you don't need to label any variables with it at all, the compiler enforces it.
I think this is especially true for line of business applications that have the following qualities:
1. It's simple. Let's not lie, let's not pretend. It's CRUD. You can put in K8S, you can add AI, it can be behind an API Gateway, you can make it all Event Driven, you can use CQRS for every entity because you really, really want to feel clever. But it's still CRUD.
2. Other people are working on it, many other people.
3. People from other companies are interfacing with it.
So knowing what to expect trumps everything. Types help with that. They help a lot.
Unfortunately this attitude is not profitable, and also smacks of someone who works on their own island and costs a business more than they add value given their inability to work as a team. This attitude might find a way to be profitable when held by a university professor, though that does not mean its desirable.
This is not a "work" attitude. I would never impose my opinions on a team. I've worked professionally in many languages with a variety of type systems. In virtually all cases I have reviewed very positive feedback throughout my career, in particular in terms of my ability to work well with others, to mentor, etc.
I actually like this approach. I believe in mentoring. I believe in building bridges. But if we're on opposite sides of the Grand Canyon, it's not worth my time.
Well, getting this personal about a purely emotional preference might make people think less of a person that does that, with significantly better justification.
To be clear: people have tried to show the long-claimed safety benefits for a long time, and they just refuse to appear.
This is a very arrogant and dismissive attitude to take. Consider that both pg and DHH prefer dynamic typing. Maybe you can think less of them as devs, but what each built is undeniable.
What have you built or added to the field that justifies such a lack of tolerance of those who don’t subscribe to your point of view?
> I am outright saying I won't engage in a conversation.
It's actually arrogant and dismissive because you say that in a way clearly intended to spark a conversation.
Also, pretty much all arguments for explicit typing suffer from mechanistic bias (see, https://www.youtube.com/watch?v=NmJsCaQTXiE&t=143s). I'm comfortable saying I just enjoy tinkering with a formal description of a data structure, because I'm a type enjoyer, not a type fan.
> It's actually arrogant and dismissive because you say that in a way clearly intended to spark a conversation.
I don't follow at all. Why would it be arrogant or dismissive to say something in an attempt to discuss it? I commented in good faith to an article that has a strong opinion ("willing to die on this hill") with a reflection of my own strong opinion ("unwilling to discuss").
At most you could say it was inflammatory, which wasn't intentional although looking at the insane number of child comments it apparently ways.
Mostly, it boils down to selfishness and "laziness". (Although I try to be very careful about that term, because I do not believe in it as generally used. I use it here to define: an unwillingness to plan ahead and to have to learn something new. Not applied as a global personality trait but only used in context.)
Types are integral part of standard programming languages. If you mean "types bad" as in BASIC/Javascript variables, then yes I fully agree. These languages were never meant to be a solution for professional software engineering.
Once a language has proper types, it can be "weak typed" by not forcing "strong typing" by being dynamically typed in nature. If the language has normal preprocessor support, the static type system can be added on the project level. And then in essence you get a strong typed environment in a very thin programming language such as C.
Let's take a big C or C++ software project, the process behind it. Of course, the project is in those languages because it requires native opaque pointers and hardware access. The project has coding style, it has arbitrary rules. Although there is little to stop anyone from making a mess in C or C++ code there is entire code infrastructure and CI/CD chain around it. Lets say the rule is no void pointers without encompassing struct that's a type that can be checked via macro system. If you wrongly use the type struct, you get stopped by the project build. If you go around the rule, you get stopped by a code analysis after commit (and get in trouble for doing that).
Same here. Whenever someone starts babbling about why __language x__ is hard because of types or whatever, I just disengage. I don't care what anyone thinks about types. I use them, and I will keep using them no matter what.
How often do you write bash scripts or do you always write all automation in some other language? Just curious how much your conviction influences your behavior
Speaking for myself, I never write bash scripts if I can help it (though sometimes I can't help it). That isn't so much because of typing, though... it's because bash is a fucking horrible language that should have died 30 years ago. The fact that there's no such thing as types in bash is just one of many reasons why it sucks to use.
I write code without static types all the time. Daily probably. I am "pro types" in a hand wavy way. My conviction is to no longer engaging in the debate.
You're confusing static typing with the existence of a type system. As an example, Julia has a rich type system, but the language is dynamically typed.
if you get the static type system wrong once, there's no going back
Take for example, Rust: mut, Send, Copy, Drop, Debug, dyn have all their warts because they are special and you can't fix them because you'll break previous code.
In a gradual typing language you could potentially bolt-on your own type system as a package where it just runs the type checker if you want one. It just so happens people who favor these gradual typing systems don't do a good job of creating those typing systems because they are not that into types in the first place.
But in theory, this kind of a system would let you not worry about types when prototyping the system, then put some bounds later based on your requirements.
I'm with you, especially in my professional life. That said, I think Clojure is interesting and Rich Hickey has some good talks in support of dynamic typing.
type ":t" in ghci for many common Haskell types (functions are also types fyi) and tell me if you have the faintest idea what you are reading
lots of comments here praising Haskell and Rust frankly seem to be coming from people with a superficial understanding or limited exposure
I've had rustc complain about type deductions that were over a line long...that's just one inferred type...you can easily paint yourself into a corner with a type system with no exit other than trying to cajole the right definition out of the compiler and then you copy-paste it and pray
This is not about type annotations, it’s about type systems. You can write Haskell programs without any type annotations. Your program will type check and compile just fine. The value comes from the checks (as well as the inference, documentation, optimization, and everything else a modern type system gives you), which you don’t have to write yourself, you get them for free.
People who say they prefer dynamic languages are really just saying ‘no’ to all these free benefits.
If your language is wholly type inferred, and the type system is expressive enough to deal with whatever one wishes to write, it is indeed "free". If you ignore compile time performance, run time performance, and that you're writing in something between Idris and vapourware.
Otherwise the cost is merely being unable to write anything that the type checker does not understand, and however long your compiler takes to do the checks on what it does understand.
Also a fair chance the whole program must type check before you can see the results of changing a subset. In the worst case you get to hunt down all the unused variables before it'll run the test suite.
I prefer dynamic languages. That makes me a heathen on these boards, to be ignored or chastised for my stupidity. Regardless, those benefits do not come for free.
Even though I usually fall more on the "types good" side of the discussion, the advantages you listed are not free at all. They come at the cost of at least:
- having a compile step in between writing and running your code. This drastically slows down the feedback loop of development. The more your compiler has to check for you, the slower it gets.
- being able to run your program in a half-broken state. This may not seem like much of an advantage, but sometimes it is good to just be able to run a broken piece of code to see how it crashes. This is especially important when learning to program, but is still very beneficial when learning a new language or framework, or sometimes for debugging.
- As someone who has contributed to the main Haskell compiler, I can definitely confirm that a more complicated type system can slow down development of the language itself too. It takes significantly more effort to grok all the possible interactions as the codebase of the compiler grows.
Don't get me wrong, I think encoding and enforcing program properties with types is a great idea that will grow further in the future. But the dynamic languages gained popularity for good reasons, and some of those reasons are still valid today.
If you are proponent of strong typing, you need something that is effectively a theorem prover. I.e when you define a type, you define the scope of the data it can hold, operations on that data, and the resultant types of those operations. That way, when you code compiles, it is by definition "correct".
When you accept anything less then that, you are basically making a statement that you are willing to forgo some of that correctness for convenience, which is fine, but that means that no language out there is really good or bad.
Theorem provers' type systems are not necessarily stronger than SML's or Haskell's; they are more expressive, which is a separate thing entirely.
That said, your position is also just weird to me. Yes, theorem provers have nice type systems that are wonderfully expressive. The trade-off is that some common programming patterns become difficult to use or are even impossible.
When it comes to everyday programming, I don't think theorem provers are at a point where they are particularly useful. Not everything needs to be proved formally, and I don't think this position is at odds with the belief that static type systems are generally "better" than dynamic ones.
> The trade-off is that some common programming patterns become difficult to use or are even impossible.
Not really. Strong and expressive typing at its core is simply creating data packaging containers that have defined operations on them. It says nothing about logic. You may be referring to the functional programming aspect that comes with strong typed languages, which is related but not the same as strong typing.
The point is that typing is just a tool that a programer can use, whether its built into the language or ran statically like MyPy. You can take a piece of code in C, and write a test suite on input and output of that piece of code, and accomplish much of the same thing that typing accomplishes. However, in the case of strict+explicit typing, the idea is that you wouldn't need tests in the first place, because your code would be correct by nature of compilation.
Some theorem provers, such as Coq, are not Turing-complete. This means you cannot write some programs, and in particular you cannot write infinite loops in Coq. Infinite loops are a common pattern (e.g., a REPL or a GUI display waiting for input).
This is a trade-off, as I said. You gain the expressive type system, but lose the ability to implement certain programming patterns. It has nothing to do with the strength of the type system.
---
> whether its built into the language or ran statically like MyPy.
All (true) type-checking is static, so MyPy "running statically" is not a noteworthy feature to distinguish it from other type checkers. I guess this point may seem trivial to some, but the broader context of this conversation involves conflation of the terms "static type system" and "strong type system", so your misuse of the word "statically" here seems worth pointing out.
That said, whether you use an external tool to check your types or the type-checker is built into the compiler is irrelevant, and I'm not sure why you brought it up at all.
---
> You can take a piece of code in C, and write a test suite on input and output of that piece of code, and accomplish much of the same thing that typing accomplishes.
Depending on the perspective, this is factually incorrect.
Type-checking is an ahead-of-time operation that guarantees the absence of certain classes of errors at run-time. Writing tests to check for the absence of such errors is not equivalent, because you have not proved anything; you merely gain confidence. They are semantically distinct, even if you write many tests to gain a lot of confidence.
>Some theorem provers, such as Coq, are not Turing-complete. This means you cannot write some programs, and in particular you cannot write infinite loops in Coq.
You are talking about languages, Im talking about the concept. The modern theorem provers aren't up to the task. Due to Rices theorem, you cannot "fully prove" a program, so a language that does this couldn't even exist. The strict typing however can be applied to subsets of the programming space, namely the data processing pipeline, whereas higher level stuff like the actual server code that does have an infinite loop to listen to requests can be written in whatever.
The point is that no language recommended for strong type safety today is anywhere fully complete to include the rigor of something like a theorem prover, and anything less then that is basically your own opinion on what is "good enough".
>Type-checking is an ahead-of-time operation that guarantees the absence of certain classes of errors at run-time.
Run time errors are no different than compile time errors as far as testing is concerned. Its not like the computer blows up when you have a seg fault. And you absolutely can prove what you need for operation.
Say your input to your code is an HTTP request of length x. And output is some data processing on that request. You can write a test suite that is basically like this
1. Ensure that code returns a well defined error for x values outside of given range.
2. For all valid ranges in x, test all possible values of every byte in that range, and ensure correct behaviour.
While overkill, this will absolutely exercise every single piece of your code and prove correctness. You can also couple this with checking things like memory access
SML is good enough for theorem proving. That's what it was written for. Though you probably want convenience libraries on top, maybe the one called HOL.
if I store sql with a column definition that stipulates that the value must be an int between 1 and 3...what difference does it make if I accidentally create a corresponding variable with the value "fred"? it will never be persisted
furthermore, you can run in to even more confusion with a language type that is not properly aligned with the database type...which one wins? the db obviously, since language values not aligned with database definitions will never be persisted
Yeah I've been in that camp since like 2016. TypeScript is basically an essential for me now, and I don't debate it with anyone. If they don't get it, it just gives me a baseline understanding of how little experience they have in real world software development.
I'm all for healthy discussions, but I think it's fair to say "I've had this discussion enough times, unless you're really bringing something new to the table, I'm opting out".
Is it really a healthy discussion? At least with respect to JS vs TS (which is the most common argument as it's a choice every front end team has to make), it's blogs saying "fuck types, I like JS only." I'm not going to step in and tell another team what to use but whining about the difficulty of static typing is just not a productive conversation. I've been converted by the top level comment; I'm not longer interested in entertaining the discussion.
I don't use dynamically typed languages because they're dynamically typed. I use them for other reasons and they happen to be dynamically typed.
I'm not going to argue that these features are only possible because of dynamic typing, but regardless, statically typed languages tend not to have them.
Elixir may get "some form" of typing, but it likely won't be traditional static typing.
STM, REPL, and data driven nature of Clojure. I know most people don't use STM in Clojure, but I find it to be a killer feature for some projects. Combined with pervasive immutability and it enables concurrent designs that are very difficult to pull off in other languages.
For Elixir, the concurrency model and supervisor trees. It's a perfect fit for some problems, and in general as a small company, the projects we use Elixir on greatly simplifies our production environment which is always a win.
Most of my focus when designing a project is to enable people with less experience than me to contribute in a bug-free fashion in the face of concurrency and parallelism. Sometimes that involves picking a funny language, sometimes it doesn't.
I find it interesting that people in this thread seem to have absolute certainty that "types good" is true, while to me those two words together are largely non-sensical, just like "bytes good" wouldn't make much sense to debate.
After you repeatedly waste a bunch of time debugging things that static typing would have caught on the first compile, and you start developing a "types good" attitude. If you just do simple React development, you might not ever run into the problem. But once your programs get to a couple thousand lines, you start occasionally forgetting what your function takes and passing in the wrong things, which then get passed around for a while and throw an exception long after the problem happened. This results in very unfun print-debugging, particularly in a recursive descent parser or an algorithm that processes a lot of data.
>But once your programs get to a couple thousand lines, you start occasionally forgetting what your function takes and passing in the wrong things, which then get passed around for a while and throw an exception long after the problem happened.
I've been writing code for 40 years, I can't estimate how many loc I've written, but likely over a million. One of my personal projects is currently over 65k loc vanilla js, and I never once had a problem with not knowing what type a function took. If you're so bad at naming things and knowing what a function does, I guess maybe types can help you. But not everyone needs it.
I'll take the time to reply to both of you, because you seem to have opposing views.
I think such a debate is largely unproductive. Like anything else, types have value (ha) and successfully capitalizing on that value depends on the context of the project, which includes things like developer experience, tooling, project complexity, requirements, deadlines etc.
The only productive outcome of these debates is that each developer gets to slowly and frustratingly build a list of pros and cons as they go through the arguments presented by either side debating this topic. In addition, developers who completely disregard either the cons or the pros are necessarily making subjective decisions with incomplete data, and the project gets to pay the price. Just because the developer is personally OK with all of their projects paying that price, doesn't mean it's the best decision for a project.
My experience has been that when starting out, projects get the most value out of not having types, and as they grow in scope and size, and the cons of not having types start creeping up, that's the point when gradually transitioning the code to being strongly typed allows the project to maintain its velocity _and_ quality.
So... about the strong part in 'strong static typing':
Many years ago I thought that it's a good idea for a game math library to have separate strong types for 'point' (a location in 3D space) and 'vector' (a direction and magnitude in 3D space), and allow/disallow certain operations (e.g. 'point + vector => point' 'vector + vector => vector', 'point - point => vector', while 'point + point' is illegal).
Sounds absolutely great in theory, but in practice it was a royal PITA to work with, but it took me much too long to realize this (how can it be such a hassle when in theory it's such a good idea!)
I soon went back to a general 4D vector class (where a 'point' is defined by .w = 1.0, and a 'vector' by .w = 0.0), and some debug-mode runtime validation (which catches things like trying to add a point to a point).
Of course strong typing also often makes perfect sense, for instance in a 3D rendering API it should be a compilation error to provide a texture-handle where a buffer-handle is expected, but after this experience with points vs vectors (which should've been a classic showcase for strong typing) I would never again "die on that hill" :)
TL;DR: static typing: yes! strong typing: it depends.
> For things like kilometers versus hours I'm still undecided.
Those help you more if you do a lot of math involving both them, but that’s also when it becomes a nightmare in most programming languages because of an explosion in the number of types.
Let’s say your code computes
3km × 4hours
If so, you need a “km hour” type.
In many languages, you also have to write code to make that happen, and to make it have the same type as
4hours × 3km
Even if you don’t ever store values with those types in variables, you also may need types for per km, per hour, km² and hour² for expressing the types of intermediate values (for example, in a physics computation, you may encounter √(3km²/4hour² to compute a velocity in km/hour)
And that’s ignoring that you may
- encounter minutes, meters, yards, etc.
- want to use algorithms that compute exp(3km) or log(4hours). What types do these have? Here, you probably want to forget about string typing values.
Apologies, I edited my post while you wrote your reply and removed the kilometers vs hours thing. But your reply makes perfect sense, and I think this "type explosion" is my main gripe with too extremist strong typing.
That's not a shortcoming of strong or static typing, that's just you realizing that your initial data model was flawed and that you either needed to unify them into a single type (like you did) or utilize an abstract class/interface.
The good thing about stricter typing systems is that it forces you to handle all the cases when you're doing a refactor like this before it compiles.
When changes to the data model happen in large codebases of dynamic code, it frequently gets shipped in a non complete state and turns into a production runtime error later down the line.
discussions are a two-way street. if you aren't getting it, then how do you expect someone else to "get" your position?
"The whole problem with the world is that fools and fanatics are always so certain of themselves, and wiser people so full of doubts"
-- Bertrand Russell
[edit: To clarify, i am on the "i like strong static typing most of the time but also understand their pros/cons wrt. dynamic languages" side of things]
First way is to use a strongly typed language, think about the data, and design your code in the appropriate way. You code it up, go through the loop of compiling and fixing errors, and get your code to run.
The second way is to write your code in Python, without worrying about strong types. You complete the code quite a bit faster, but since you also want your code to be correct, you spend time writing an end to end test suit for your code.
The second approach is not only faster (since you are writing tests in both cases), its overall better. Spending time writing tests allows you to essentially validate things that modern mainstream strongly typed languages can't catch at compile time (for example, what happens when the input is unicode strings?). It also forces you to think about end to end behavior and making sure that is correct, rather than just the behavior within your code.
Strong typing is a simply hand-holding tool for programmers. If you cannot write correct code without it, you are on a fast track to being replaced by AI eventually. The future of programming is not going to be designing data structures and types, its going to be using English to generate large chunks of code in most likely Python, and then tweak fine details in those.
Call me sceptical, but I don’t buy it. What makes you certain that’s the (one) future… Also programming is a super broad field, and more then a field is almost more like a medium then a field.
I honestly don’t know if we’ll still program by hand or not, but I do look skeptical at people who are very certain we won’t. Don’t think that’s the first time in history people make that prediction…
Compilation at its core is a translation problem. Right now, it would be fairly trivial to train an LLM to essentially be a compiler. And, you can already prompt LLMs on generating code for a wide array of things.
As far as adoption, there is a reason why dynamic type languages that feature a lot more natural syntax (Node, Python) are used WAY more than others.
You can, but the reason you do this in practice is because the static typing languages are often not sufficient or strong enough. And if you are writing tests, you may as well do everything through testing and focus on rapid dev rather than worrying about types.
I think this is completely false. It completely breaks down as soon as you get into your first refactoring that is a bit larger than completely trivial. Typically the mess one ends up is one or more of the following. (1) Nothing is refactored because people do not dare to do it. (2) Not just some but most of the uncommon code paths are broken. (3) Tools that are used to interact with a running application are regularly in a broken state. You want to prevent these problems with 100% test coverage. As such this may be a laudable goal on occasion but I do notice that the code described in (2) and (3) are highly non-trivial to cover 100%. I also have to wonder what mythical developer is does not have the discipline to use static typing but does have the discipline to attain 100% code coverage. I first want to see such a developer before I believe in his/her existence.
>It completely breaks down as soon as you get into your first refactoring that is a bit larger than completely trivial.
This is, in fact, MUCH easier to do with a strong test suite that only cares about input and output rather than internals.
The most common approach to starting a refactoring project is write or enhance a test suite to the point where there is no undefined behavior, either the program works or handles the appropriate errors.
You don't need to aim for 100% test coverage either, you just need your tests to cover all possible inputs (including fuzzing).
I don't know, that is so last millennium. If you prove your software correct, you won't need a static type system, it just gets in your way. And that's a hill I am willing to die on.
Sometimes the question isn't "are types good", it's "are the costs associated with static types worth it for this particular use case". And the answer is almost always yes; but the "almost" there is important.
This is the sort of position that is baffling to me. Software engineers claim to be very logical, but this boils down to a preference being stated as some kind of objective truth. Show the evidence (not anecdotes) that static typing gives benefits that are worth the cost. My anecdotal evidence is that I rarely come across problems in production code that would be solved by static typing and people make the same sorts of logic mistakes in languages like Java and Typescript.
Null reference errors are examples of errors that can be caught before runtime by a static type system. The fact that static type systems like Java don't catch this is evidence that static vs. dynamic is not a binary - some static type systems are better than others.
I think the conversation becomes a lot easier once you recognize that there is no such thing as untyped data.
There's only explicit typing and implicit typing.
So the only real argument you're having with someone is when writing a piece of code, do they want the caller of the code or the input of the code's data type to be known or they want the data type to be a mystery to be figured out, occasionally in production when shit hits the fan.
> I reject the idea that pg/DHH should be treated with any authority here, and I reject the idea that I need to prove my authority to you.
> That said, yes, of course my post is arrogant and dismissive. I am outright saying I won't engage in a conversation. I'm comfortable with that.
This is an extremely toxic attitude. I wouldn't be interested in working with you in any professional capacity, on an open-source project, or having you as a friend, and as a matter of fact if someone expressed this attitude at work I would complain to HR.
All kidding aside, obviously I don't. That shouldn't detract from my statement at all though, nobody measures anything about software. We're all in the dark. Thats actually reinforcing my beliefs: we are in the dark because software is unmeasurable because of the freedoms taken by programmers that fail to abstract or fail to be formalized.
Formalizing software, and then starting to measure changes rigourously, is the first step we have to take towards becoming a mature industry.
Formalization is the oposite of freedom. People think software is like art, where freedom of expression broadens horizons. This is no longer true, most software is not art, most software is buttons that make money when clicked.
Your statement reminds me of the spoon scene in The Matrix: Instead of trying to bend the code to fit within the confines of its types simply try to realize the truth... Static types are just more code. They're extra conditions that we apply to things like function signatures and variable definitions instead of adding conditional checks where it matters.
"Aha!" you say, "but by enforcing conditional checks at variable definition and inside function signatures we don't have to check them ourselves in our code and the compiler itself can check them to ensure correctness and optimize performance!"
...and I'd agree with that sentiment but I'd also add that by having to reason about your types constantly you're adding a non-trivial amount of mental overhead when structuring your code. For some people that's how they reason about their code anyway but for others, having to consider whether or not a number is a `u8`, `u32`, or `usize` adds a lot of complexity to the code that could very well be entirely unnecessary depending on the use case.
Consider, for example a program that's meant to be executed on the command line that takes two numbers and adds them together. No matter what language you use you have to convert the raw string input into a numerical type before performing the addition.
In Python you have a simple function, `int()` that will happily turn any string consisting of numbers into a proper integer that can be used for math operations. If it gets something that isn't a number it'll throw an exception which is trivially easy to detect and wrap in a `try`/`except` block with a user-friendly error message. If we wanted to support floating point numbers we could just use `float()` and the code would otherwise be exactly the same.
In strongly-and-statically-typed languages this same process involves considerably more overhead. Before we can select a function to convert a string into a number we must first pick a numerical type and before we can do that we must think real hard about how big of a number we're going to support in this application. Our function signature(s) could also get complicated because, "wait: what type of string are we going to get from the command line?" Something as simple as adding two numbers becomes complicated really quickly!
Comparing the two ways of handling things (strongly, statically typed VS strongly, dynamically typed), Python ends up the clear winner a lot of the time because you got the job done without having to think as much, with less code that's super easy to reason about even though it didn't use static typing.
The other problem is that when you write such trivial programs in strongly, statically-typed languages like C, C++, Rust, etc it requires a lot more mental overhead to look at the code to figure out what's going on/how it works (though with Rust, less so because there's not 1001 awful ways to do things haha).
> Before we can select a function to convert a string into a number we must first pick a numerical type
Except this has literally nothing to do with static typing and everything to do with whether or not the language you're using cares about numeric types.
For instance in typescript you'd just have `number` as the type. You could conceive of a dynamically typed language like python where adding a u8 to a u32 would cause a runtime error. In which case it sure would be nice to have the compiler tell you to convert before you called a function.
>The other problem is that when you write such trivial programs
If only I had a career writing trivial programs that would make a lot of things easier.
> In Python you have a simple function, `int()` that will happily turn any string consisting of numbers into a proper integer that can be used for math operations.
For starters this is not the case, it does not work on a string of numbers:
>>> int("123 890 123")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '123 890 123'
> Before we can select a function to convert a string into a number we must first pick a numerical type and before we can do that we must think real hard about how big of a number we're going to support in this application
Aren't you doing the exact same thing in Python by picking `int()` over `float()`?
> what type of string are we going to get from the command line?
Well, this might be the case for system languages, but most have a single `String` type.
if you abuse the type system enough, most languages will have weaker typing.
"the language" is not enough per se, you have to use the tools it gives you to gain some advantage: those projects were clearly actively avoiding strong typing, for whatever reason
I, too, find tools hard to use after I intentionally break them.
It's unfortunate that Typescript even provides this casting hack, though it's necessary for gradual, optional typing. The very first thing I would do on such a project is spend the time to fix those broken types, because it will pay back in full in productivity after a short time.
I think this comment doesn't add to the discussion.
It's like saying you can kill yourself in any vehicle.
Might be true (or maybe not), but doesn't add to the discussion of vehicle safety.
> We have projects in java7 still which span thousands of LoC and juniors can just jump in using their IDE and make some sort of contribution.
This seems to be a main reason certain people like it. It gives a sense of productivity. However I think it is misguided. It’s a false sense of productivity. Can they commit? Maybe, yes. Will it be right? Maybe, and more likely with types to handhold. But does it mean they understand the data model and the domain of the codebase they’re working in? No. No typing forces you to know what your code is actually doing.
It takes longer to be productive, but you’ll be productive because you know what you’re doing. Not because the IDE held your hand.
Widget factories or scrum farms will no doubt like the “ease” of jumping into a codebase that has typing, but I’m not yet convinced it’s better or that much better for the experience developer. I need to think on it more. For certain though I’ve seen enough in my time to know that how quickly a junior can “just jump in and make some sort of contribution” is not a good measurement.
Your argument sounds like a language should make it hard to understand parts of a codebase without understanding the underlying parts as well.
I would argue this goes against the goal of abstracting problems to keep complexity manageable.
Of course abstractions can be misunderstood and misused, but is that an argument for not having them?
Nope. A new dev will need a lot longer to understand a large python codebase than a java one.
In fact, if all the developers change, it's a virtual certainty that none will ever understand the python code, while the odds are about even for java.
(But then, the types aren't the only factor for that.)
"Truly understanding what goes on" and "static typing" are in no way correlated, let alone causations.
You are making a false dichotomy.
It's perfectly possible to work in a dynamic codebase without understanding the domain, business, logic or big picture. And it's just as perfectly possible to build a statically typed codebase that forces you to understand the whole entirety before being able to make a change.
Now, whether it's actuallygood to enforce true understanding of the Whole, before being able to work on a subset, is another debate. One that, unsurprisingly, has long been proven to be false. It's why we consider modules, functions, boundaries, coupling, classes, microservices, layers, and so fort and so on.
One might equally say that the embracing of reflection in Java and C# pretty much solidifies the case against static typing for complicated projects.
I'd tend to think the important feature here is strong typing, not static typing, and indeed TypeScript, which is hampered by the anemic type system of its parent JavaScript, it not the best example of strong typing.
Ultimately, strong typing is a hill I'm willing to die on, too. But a lot of people making arguments on this topic are conflating it with static typing, which just isn't the same thing. In general, that gives me the impression you haven't done enough work in enough different languages to really have an informed opinion. The type systems of different languages work within the context of the language as a whole, with tools like REPLs, test frameworks, constraint checkers, static analyzers, and IDEs carrying a lot of the work that might be attributed to a type system. There are a lot of confounding factors here, and if you're only talking about a few languages and you are making mistakes like conflating static=strong and dynamic=weak, you don't have the breadth of experience to understand what the benefits and downsides of strong types are.
Though uncommon, strong dynamic type systems do exist. Scheme is probably the most popular example, although its type system isn't the strongest. This is the approach I'm taking with Fur[1], which attempts to have as strong a type system as possible while still being dynamic. Python is... stronger than many other popular languages (i.e. JavaScript), but still does a lot of coercion (particularly around truth-y/false-y values--duck typing is a bit of a grey area in that it's sort of arguably weak typing, but also arguably indicates a capable type system rather than a weak type system).
But perhaps more critically, weak static type systems also exist, such as C, which is the source of many of the world's most serious bugs. It's highly unwise to assume that static typing is strong typing.
That’s why we like Python and Elixir but not Javascript which is weakly typed. :-)
Since the author is willing to die for it, they might want to know that they probably meant “dynamically typed”. Some dynamically typed languages are strong typed, too!
Author here. Please read the post, I actually said "Strong static typing" in the post itself (starting from the first paragraph), it just didn't look good in the title. :)
That’s an important distinction. Why not just use “Static Typing”. Yeah, the word “strong” sounds strong and cool, but since it’s something you’re betting your life on, it doesn’t hurt to be a bit more precise.
Yeah, but your first point is specifically a weakness of weak types, not dynamic types.
I don’t really see any arguments against dynamic typing in there.
Most strong, dynamically typed languages also have good developer tooling (elixir and dialyzer for example) and have built in ways of describing data structures.
There's a section about catching everything at compile time rather than runtime which is very much about. I think there's a lot of value at knowing when things go bad at compile time, rather than runtime.
I like the idea, feels very pythonic ironically. And I suppose if you don't want the generic, I imagine there is probably some subclass of that which is more restrictive.
Anyway, as a developer I love when people do this. As a developer this sounds like a nightmare to maintain. My coworkers have told me to limit the numbers of 'what ifs' and just roll with it.
You can think of generics a meta-language that restricts and documents some behavior one level above the concrete types that you might need..
Lots of benefits, code re-use, documentation as code, less bugs..
Granted it's not always easy and straight forward but that what senior developers in the team are for..
I don't care how many times I see this argument; strong typing is correct. We need to keep beating this drum because every year there are more and more new developers that know nothing. We need to make good one that aren't ignorant of history and what works...because the more things change the more they stay the same.
I wonder if most opposition (or even ambivalence) towards strong typing comes from never having used it. If your entire career has been JavaScript, Python, shell scripts, etc.; then you have zero experience with it.
I wonder if most opposition (or even ambivalence) towards strict typing comes from never having used it. If your entire career has been JavaScript, Python, shell scripts, etc.; then you have zero experience with it.
You probably mean static type checking. I've used strong typing in the first half of my career and have no hard feelings about it. So no, the opposition isn't about no experience with one or the other.
I taught myself Turbo Pascal in middle school (34 years ago) and C/C++ in high school. (Both typed).
Over the years, I have done programming with C, C++, Java, C#, PHP, Python, OCaml, Lua, Nim, Golang, Rust, Objective-C, Flash, Bash, D, Scala, TypeScript, assembly, PL/SQL, F#, and a few that I am forgetting.
Compile time type checking can definitely make things easier.
But for dynamic languages that I have been programming in for many years to have types shoehorned into them as an afterthought feels like a kludge.
And it's a tradeoff. I got used to developing in Node or Python with vim without auto completion (although I had those things as a kid in IDEs for other languages and they can be great). And without compile time type checks.
You can pass JSON around and literally never do a database migration.
This stuff will lead to more runtime errors initially, but also means more streamlined code. And you have to do thorough testing regardless.
TypeScript might be a choice for a large project with several developers. But I would also argue that an even better choice would be just to use a different language that has the typing system you want built in rather than awkwardly bolted on.
What I see in most TypeScript projects is a half-assed attempt that still results in run time type errors, but with the added "benefit" of an awkward bolted on typing system and the need for more tools as well as losing any dynamic benefit.
They drop the idea of using JSON and make everything related to the database compile time checked if possible. I'm not saying that those checks can't be useful but if are going that route it would make more sense to just drop the dynamic language.
Having all of the dynamism can be a convenience and save you time and effort in other ways. But not if you treat it as a poor man's static language and shoehorn in types in a half assed way using a traditional database.
Part of this is that people think software engineering is about your choice of stack or adding processes to development.
What matters most is probably the feedback loop between the users and the developers. Starting with the requirements engineering. Second to that would be details of the software design and organization. Leveraging existing code effectively can be key. Using descriptive but relatively concise identifiers, short functions, good code organization.
But diving into and evaluating all of that stuff in detail takes effort and a lot of skill and so many make snap judgements based on surface level processes or tool selection.
Seemingly random changes between float64 and object happen when doing unit testing(pandas).
Can't remember the exact situation because I switched to While permanently, but there was some For loop problem. If the data came in as length 1, the for loop would read the individual chars of the string. If it came in as >1 it would iterate through the list.
15 years ago when I started python, maybe it made sense. Probably ~5-6 'oof that was a bug I wasted time' in 3 years professional use. Today, I find myself forcing types to solve a bug, or at a minimum checking the types as the program progresses.
Pandas's flexibility is amazing for exploration and prototyping. I'm actually a huge fan of the gradual typing approach, whereby you can start just messing around with stuff in a notebook, and then add the types and the rest of the structure once you are ready to solidify your design.
df.ix[0]
df.loc["x"]
df.loc["x", :]
df.iloc[0]
df.x
df.x[0]
df["x"]
df[["x"]]
df[df["x"]]
df["x", 0]
df["x"][0]
df[:, 0]
# and probably a few more I forgot
If you stop working with it for 2 months you forget what each one is doing and when you are supposed to use it.
I'm not clear what your argument is. Many of your examples are just syntactical sugar which you can very easily ignore, and even prohibit in your linter.
Also, note that pandas's datafrimes shine when you focus on column operations, if you're finding yourself accessing individual elements by index often, that's usually a sign that you should switch to a more appropriate data structure.
Looking at random people's code online is like staring into the abyss. I sometimes see APL code and find myself running in circles and screaming for a bit before I regain my composure, but I don't think that's reason enough for me to complain about APL's design.
There are alternatives to Pandas, for example Polars doesn't have this problem.
Sure, doing an operation in Polars can be more verbose, but it really does have just a few ways of doing things. Which in the end is actually more productive, since you don't need to always google the syntax.
Strong typing is what allowed me to be a developer. I was never a great developer but I was good enough to be worth my salary. I was not smart enough to maintain a complete mental model of a a weak-typed software but I was able to do fairly complex stuff with the safety net of strong typing. My software developer would have ended much sooner (or never have been) without strong types. I had not other choice but to live or die on that hill.
Static typing is useful if you are building some program for other people to run that you want to be reasonably robust.
However, it does not work well with interactive programming. If I had to declare all the types every time I do something in Mathematica, I'd be severely annoyed for no benefit.
Common Lisp seems to be the best of both worlds, with dynamic strong typing in general and advanced compiler type inference that can correctly point out mismatched types in most cases in SBCL, leading to much easier time programming interactively.
What do you call the Common Lisp sort of typing where you can drop hints wherever the machine is guessing at the type, and it will yell at you if you do something dumb with those things. But you don't have to drop hints at all and it will still compile. It is strong when you turn it on but dynamic otherwise. Strongly typed with inference, maybe?
Personally I think Strong Static typing is what's required. But with an option to do dynamic typing to avoid faff in certain parts. Strong typing on its own doesn't really solve the problem that the author is describing.
c# pretty much has that nailed. You can do dynamic typing if you want, but you're on your own if it fucks up. Moreover its obvious when you're doing dynamic silliness.
Python is strongly typed, the problem is that its also duck typed and everything is dynamic. Sure there is type hinting, but that's mostly optional and pretty much broken as its not really supported at run time. (unless I've missed something)
What I'd really like is something like perl's "use strict;" in python.
I wish people wouldn't conflate dynamic typing with weak typing. Python, for example, is dynamically typed, but it's strongly typed too. You don't get bugs like `1 + "2" = "12"` in a strongly typed language like Python.
You can still get runtime bugs, of course, but that kind of thing would manifest as a TypeError or something. All of those bugs could be caught with tests.
I like dynamically typed languages. They are simply the pragmatic choice for the vast majority of code that will ever be written. But being dynamic doesn't mean you can't do types. Python has optional type hinting and checkers like mypy are very good. Common Lisp has features that enable you to declare types and get near-native speed binaries.
These days I do type my Python code for many of the reasons given in the article, but I still enjoy using a dynamically typed language underneath.
Dynamic typing is polymorphism at the language level. In essence, you expect most operators to behave sensibly with different types. An expression such as "a + b" resembles a polymorphic function such as "add(a, b)". You would expect it to work fine most of the time, regardless of the types. And the types in a dynamically-typed language usually aren't so many – you possibly have booleans, numbers, strings, lists, maps, sets and objects.
You're not "moving faster" if a typo is a runtime error and you're not "more productive" if you can't change a function signature without having to grep your codebase to find all callsites and pray you fixed them all.
Types are good, but as with anything else people take it too far. Making it your life goal to encode all business logic in the type system results in an even more incomprehensible mess than not having types at all. If the type name can't fit on one line in the error message, you've gone too far.
I've seen some insane Typescript type definitions. To be fair, they were defined to work with historic vanilla JS code where so many things could have been stuffed in the poor variable.
I'm forever grateful for Typescript, but I wouldn't be surprised to see some of that code in a future research paper titled "The era when types went too far".
I'm mostly fine with bad TS definitions since the types are cosmetic and you can always 'as unknown as whatever' your way out of an insane type definition. If someone nests 5 generics in Rust/C++ you'll have a much worse time.
If you're using type aliases or type inference you don't have to type out the long type, but when you get the error that you need `Foo<Bar<Baz<...<&Whatever>>>` but you have `Foo<Bar<Baz<...<&&Whatever>>`, you'll still have to spend 20 minutes dealing with it.
I think TS is a special case, since you can "just say no", but in languages with a statically typed runtime/no runtime I try to minimize the amount of <> in a type, even if they're hidden behind a type alias or inference.
Agreed. I've left a few `any`s or `as unknown as X` in place before and typically leave a comment alongside it. Something to the effect of: "This code is bad and will take too long to refactor. Every time you trace a bug back to this code, please adjust the logic and typing a little bit to prevent the kind of bug you encountered. Eventually we can replace this bad code with good code."
The first time I saw this pattern play out was with CSS vs table layouts.
A lot of web devs these days don't remember a time before CSS but, back then, we used to use tables for page layout. This was really an abuse of the `table` element of HTML, which was designed to display tabular data, but it was basically the only way to achieve many layouts like having a side menu bar, for example.
Abusing `table` was bad for many reasons, including accessibility. People started to care about the semantic web and remembered that HTML is a markup language for content, not a style language for layout. So people advocated for using CSS for layout and reserving `table` for actual table data.
But gradually this rule became both simpler and more ridiculously followed. People started talking about "using divs instead of table". They would look at a page source and if they saw `div` it would get a nod, but if they saw `table` the developer would be relentlessly abused because `table` is old-fashioned. Eventually, of course, someone reinvented tables using divs and CSS.
And then people realised the pendulum had swung too far.
There's an irony that this comment is posted on HackerNews, where the entire page of comments is hosted in a big <table> for layout. Not sure if this is good or bad!
If you have a tiny codebase maybe, but if there are a couple million lines, it's likely that a pretty significant number of functions are sharing names.
and then good luck catching that call when searching for say `asdf123`. This looks insane when I describe it like that, but you actually see it more often in JS than you'd imagine, and sometimes it may even look justifiable.
> You're not "moving faster" if a typo is a runtime error and you're not "more productive" if you can't change a function signature without having to grep your codebase to find all callsites and pray you fixed them all.
I don't see how either of these things relate to type discussions. A typo can certainly lead to incorrect code in the most strongly typed, staticest language there is (otherwise, what the hell is writing code even for?) What's worse: a runtime error or no error that produces the wrong result? Running a Python project might be quicker than compiling a C++ project. Dynamically typed languages can do better than grepping for function calls.
> A typo can certainly lead to incorrect code in the most strongly typed, staticest language there is
Yes but the >99% of possible typos (and nearly 100% of typos in identifier names) will be caught by the compiler. That's good enough for me to say "static typing helps with typos".
> What's worse: a runtime error or no error that produces the wrong result
From most bad to least bad:
1. No error, wrong behavior
2. Runtime error
3. Compile error
4. Red squiggly line telling you "this is bad" before you even compile
> Running a Python project might be quicker than compiling a C++ project
Yes, C++ is an insane language. You can have large C++ projects that compile fast, but you have to spend a non-zero amount of engineering effort. Generally "avoid templates" and "write the code in .cpp files, not headers" will ensure that you won't have hour-long compiles.
This is a problem with C++, not with type-checking in general.
> Dynamically typed languages can do better than grepping for function calls.
I'd love to hear how, I was doing quite a bit of JS at my previous job (not TypeScript, couldn't convince them :) and I never found a more reliable way to find callsites/variable usages, than grepping for the function name. And even that is not 100% guarentee, since you can call a function in some pretty insane ways,
anObject[`asdf${some string}`]
and then good luck finding this callsite when searching for anObject.asdf123
Add microservices and typos are still runtime errors, except now on another, except inside a container on another computer.
You still have to grep for callsites, although the scope is smaller. A much better way to reduce the scope is with modules - if a function is module-internal, you only have to grep that module for callsites. Better than having to scan the entire codebase, worse than not having to do it at all.
Performance with microservices + dynamic language is unjustifiably terrible if you're the one who's paying for the servers. You're already eating a 100x slowdown for using a scripting language, add microservices and you're eating easily another 100x for replacing regular function calls with network packets. I'm not willing to run a k8s cluster for a program my laptop could handle had I used the hardware better.
That's all assuming microservices are even an option, which is a very tiny percentage of all software.
You know, I‘d always been like “`from pdb import set_trace: set_trace()` and you can interactively edit. I don’t care what the type is because I can interact with my running program better than a compiler” (followed by intense nerd chortling).
You know, until the program is non-trivial: passing data between systems on a queue; using async, threading, and/or multiprocessing; compiled binary critical-performance libraries… and I’m left wishing I’d written the whole thing in Erlang.
> if you can't change a function signature without having to grep your codebase to find all callsites
Every time I see a developer make this complaint about static typing I wonder what the hell they're doing with their type definitions and architecture where this is actually a problem.
> and pray you fixed them all.
If this isn't detected for you at compile/build time you're not using static typing
While I agree with most of the post, it's just the tip of the iceberg of the discussion. Stuff like interfaces, traits, generics, concepts, inference, duck-typing, implicit conversions, etc. all fuzzyfy the notion of "Strong Typing" in various ways, some more than others.
The real discussion is not about types vs no-types, it's about the role and place for each of these bending of the rule and their associated tradeoff in different contexts. Op even takes inference for granted towards the end of the post, but make no mistake, that's a form of weak(er) typing.
For example, writing template code in C++ without frequent use of type inference (aka. auto) can be an absolute nightmare (I suspect that extends to generics programming at large, but C++ is where I have the most experience here), and the legibility gains from making use of it for iterators is broadly acknowledged as being easily worth the obfuscation for scope-limited variables. But there's also a strong case being made to avoid its use entirely in most other contexts.
C++ templates are a nightmare, because it is also a compile-time macro system. The cryptic, giant error messages from forgetting a > were legendary. Maybe this has improved, I haven't used C++ in a long time.
Java by contrast has a greatly simplified generic system; Scala improves on that with robust type inference, making generics pretty trivial to write and use.
100% agreed on all points. But even in these languages, writing generics remains greatly facilitated by the use of type inference, which is all I'm saying.
I don't have much to say about the piece itself other than express broad agreeement on what is a fairly common sensical topic, but just gonna say it's interesting how many commenters here are up pontificating on their soapbox while still clearly confused about the distinction between strong and static typing.
It is because the enterprise TS supporters are too loud.
There are many here saying they consider people that dislike TS to be idiots and they don't lower themselves to discuss with those who think JS is preferable.
They don't wanna argue, we don't wanna argue. Life is great.
And decades of startups created with the next lisp/ruby/python/perl/JS. Sign me up for those. I for one would rather eat a bullet than maintain any of the TS I'm watching people write today.
I don't think we're near the peak yet. We're still on the way up. I've seen $5M projects turn into $50M projects in the space of 5 years and nobody else in my little consulting bubble has noticed at all lol. It shouldn't take 100 devs to build a SaaS with 5 forms and a dashboard but here we are...
Guy L. Steele: "Don't you think it is ironic that type theorists who want to talk about strongly typed languages talk to themselves with an untyped language"
873 comments
[ 2.4 ms ] story [ 335 ms ] threadedit: To clarify, I am on the "types good" side of things
And then, kind of orthogonal to that, there’s also static typing and dynamic typing.
I think strong, dynamic typing is just as good as strong static typing (weak typing is objectively bad)
As long as the types of your variables don’t change from under you, that’s good, but I’m also okay with the compiler / runtime figuring out what those types are for me.
A side (but many would also say critical) benefit of types is that they act as a form of documentation that can never go stale (because they are enforced by the compiler). "Dynamic typing" does not offer this benefit whatsoever.
JS will convert types under your butt if you're not careful, Python won't.
Sure, static typing (when done well) is superior to that, but the title talks about strong typing.
That’s the crux of why I’m not all in on static typing all the time. (Especially for networked programs that expect a wide range of different kinds of input)
Having to prescribe the types I need before I actually need them goes against how I tend to build.
Almost all static type systems have escape hatches that let you go dynamic when you really really need it. Also, I bet that most of your use cases for dynamic typing could be solved with a simple tagged union. Most people bemoaning the lack of flexibility of static typing just don’t know about how tagged unions can easily emulate dynamic typing when we need it, such that we rarely even need to reach for the actual escape hatches.
> (it’s impossible to know exactly what data structure you need at the start of a project)
Thankfully data structures are even easier to change with static typing: change it, gets a ton of type errors, fix them, done. With dynamic typing you run the risk of missing a call site.
> Having to prescribe the types I need before I actually need them goes against how I tend to build.
There’s type inference for that. I personally take advantage of it any chance I get.
In Common Lisp, it's common practice to not accept code unless all such warnings are gone.
Every argument I’ve seen in favour of dynamic typing is some variation of “I like it this way.” They’re not technical arguments because dynamic typing is a strict subset of a statically typed language, equivalent to passing a flag to turn the type checker off.
Sure, not every compiler offers such a flag, but that is an argument against one particular static language (or group of languages), not an argument against static types itself.
Types are not exclusively for verification, and even if you decide it is, you can do a lot more with a type error than exiting the program.
That stance most people keep repeating is actually ridiculous. The most common usage of static type systems is to verify badly-written ad-hock dynamic ones that handle user errors.
This is just incorrect. Plenty, if not most, dynamically-typed languages are compiled. Indeed even the idea that there's a clean dichotomy between compiled and interpreted is an outdated idea: I'm not aware of any production-quality language implementations which run tree-walk interpreters entirely without compilation. Modern "interpreters" typically compile to bytecode but in some cases even can compile to native code, they simply do so in a just-in-time manner. These compilation steps are quite capable of applying type systems.
For example, if you run a Python script, you can see the results of compilation cached in .pyc files (these will be either in the same directory as the source files, or in your __pycache__ folder, depending on your configuration).
> The whole point of types is to be able to check things without running the program, because exercising every possible code path becomes increasingly untenable at scale.
Is it? I would argue that the point of types is to report errors at the place where they occur, rather than doing the wrong thing silently and reporting an error elsewhere, or simply behaving incorrectly.
If a tree falls in a forest and nobody hears it fall, does it really fall at all? If a bug happens on a code path, and nobody exercises that code path, is there really a bug?
I would never want a statically typed, compiled awk for example.
I generally think large, production, multi-developer software should be written in a statically typed, compiled language.
Strong vs Weak typing is a closer to a debate about having a name spacing system or not. There is really no great reason to have weak typing or not use a name spacing system.
Mainstream dynamically typed languages are moving in statically typed direction. Python (Mypy, Pyright and others), Ruby (Sorbent, Rbs), JavaScript (Typescript, Flow). How many statically typed languages optionally removed types?
Personally I think Python moving in the direction of static typing is a mistake, dynamic typing is very useful for domains where python is strongest: modeling, statistics, scientific computing etc. It's also part of the basic design of Python to be a dynamic language. Likewise Ruby, with it's heavy use of metaprogramming, also benefits tremendously from a lack of types.
But let me be clear: I do think statically typed languages are a very good idea for large production systems, I just personally do a lot of programming that's not for these systems.
> How many statically typed languages optionally removed types?
I wouldn't say "removed" types, but I've been in software along enough to remember when dynamic typing was the big hot thing and crusty old Java devs complained that we couldn't possibly live without static type annotations. I distinctly remember when C# introduced `var` (which is of course really type inference, not dynamic typing) to appeal to devs that were growing weary of types.
There's a great example in SICP of implementing a full object system in just a few lines of code that would not be possible to implement as elegantly in a statically typed language. Do I want that for a production system? No. But there is, or at least used to be, a world of computation being done for reasons other that quickly getting PRs pushed out to prod.
> I can see both side of the arguments on many topics, such as vim vs. emacs, tabs vs. spaces, and even much more controversial ones. Though in this case, the costs are so low compared to the benefits that I just don't understand why anyone would ever choose not to use types.
> I'd love to know what I'm missing, but until then: Strong typing is a hill I'm willing to die on.
I genuinely want to know what I'm missing. I also outlined in the post all of the arguments I usually see in favor of not having types and why I disagree with them.
I'm happy, willing, and excited to hear what I'm missing.
Is there a language out there that gives you that choice?
I expect you mean choose not to use strong static typing as per the original piece? Compatibility is a pretty good reason. I'd like to see Javascript die in the fiery pits of hell as much as the next guy, but its positioning means it is almost inevitable that some system will make it the only reasonable choice for you to choose if you want to build for that system.
Typescript doesn't help. It adds static typing, but not strong typing.
Usually: A strong type system does not allow types to change after being established. A weak type system allows types to change. This is also described in the original article.
so... like in c++?
Generally "strong" vs "weak" typing[1] is an ill-defined an mostly useless definition.
[1] as opposed to static vs dynamic or safe vs unsafe.
https://en.cppreference.com/w/cpp/language/explicit_cast
edit: I guess what you want to say is that implicit casts make a type system weak. But even there there is plenty of wiggle room: I think that everybody agree that implicitly converting the string "1" to an integer is bad (which is not allowed in C++). Narrowing conversions are arguably bad (they are sometimes allowed in C++), but some other implicit conversions are hard to argue against (int to long for example, or derived to base).
For dynamic types, sure, that's a reasonable enough definition. But it doesn't really make sense for static types: they're attached to expressions in your source code, not runtime values.
In order to have that, you need static typing of variables and constants, like most statically typed imperative languages have. But you also need a method to specify required input types and expected output types of all functions.
In a purely functional language like Haskell where there are only functions, and functions are first class and can be both inputs to or outputs from other functions, then the entire operation of the program is encapsulated in its function type signatures.
The entire flow of data and logic through the program can be type-checked by the compiler, and function implementations checked against their type signatures.
Or do you mean that the C-based escape hatches like casting pointers make the type system inherently weak? You don't have to use them though...
Said UB is observationally indistinguishable from miscompilation under some toolchains under some optimisation controls.
It also has various bolt on weirdness like const doesn't mean the thing won't be changed by some other pointer so you can't constant propagate based on it, unless it's written on the global, at which point attempts to mutate it anyway may succeed under the usual UB challenges.
Maybe that's a "strong" type system, but you'd only define it like that if you started by taking C++ as axiomatically reasonable.
> I'd like to see Javascript die in the fiery pits of hell as much as the next guy
Part of the problem is that some of those "next guys" don't have the experience and/or vision to realize that it needs to die. Perhaps we should emulate Cato in our subsequent HN posts:
And furthermore, I consider it necessary that Javascript be replaced with WebAssembly so that we can use well-designed languages in the browser.
As soon as there's any complexity at all or another person involved that exception stops.
Now, in general, I agree with you. If it's a one-off (or if it's really never going to need maintenance), and if it's small enough that you don't need types while writing it, then sure, do whatever is easiest at the time. But "never going to need maintenance" often turns out to be a lie, and when that time comes, you may be happy for some types as signposts to give a hint of what you were thinking all those months or years ago.
Otherwise, if you're unable to source another script, you could just have another script return a normal result in addition to a JSON blob of the environment variable changes the caller should make, which is probably worse than just allowing sourcing.
Pipes allow arbitrarily complex networks of communicating sequential processes. In some cases, networks of tiny CSPs are cleaner, but without discipline, they can rapidly become worse than huge monoliths.
Particularly as programs start out simple and organically grow, once they start hitting length limits, they're going to start evolving into locally-distributed computation. Maybe you get something nice like composable small unix-like utilities. However, I suspect there's a large overlap between the set of developers who would do that well and the set of developers who would still keep things nice and modular within a single process if they didn't have size limits.
Also, type weakness is more a property of the runtime than the language, but for those languages with specifications, the language specification usually also specifies a large amount of behavior for a compliant runtime.
The C runtime, will gladly (with UB nasal demon caveats) allow you to treat an int as a pointer. There are static type checks, but no hard limits on the type-confused nonsense it will attempt to execute. C is statically typed, but weakly typed.
Java, C#, etc. are strong statically typed. There are static checks at compile time, and the runtimes do their best (modulo escape hatches) to use dynamic runtime type checks to plug the gaps in the static type systems. (For any sufficiently complex sound/consistent type system in a Turing-complete language, as per Godel's incompleteness theorem and the halting problem, there will be some programs that cannot statically type-check but still will never encounter a type error, regardless of input. So, in practice, there will always be escape hatches in the type system to allow programs that are correct but not provable. Hopefully most of these escape hatches are backed by dynamic runtime checks.)
Of course, these categories are a partially-ordered continuum, not clear binary distinctions. For some pairs of languages, you can say one's statically enforced properties are a superset of the other or that one runtime enforces a strict superset of the other's dynamic checks. However, it's often a case that you can't say one language strictly offers stronger static type guarantees or one runtime strictly enforces stronger dynamic type restrictions.
https://github.com/coalton-lang/coalton
What Lisp are you using that doesn't have types?
The vast majority yes, that's why I was a bit confused :D
There some languages that have no types. The only thing I can think of though is a POSIX shell minus arrays. (Edit: Assembly and Forth are two better examples)
> Most Lisps do not have a static type system
True
> and even fewer have a "strong" type system.
Common Lisp, Emacs Lisp, Scheme, Hylang, Clojure, and Racket all feature strong typing. I'm curious where you have found this trove of weakly typed Lisp dialects
I think that interpretation of the "strong" vs "weak" scale is a valuable one within the context of the blog post. The post is at least partially about how type systems can help the programmer by making them aware of certain kinds of errors (it is also about when: static vs dynamic).
My understanding of the terms covariance and contravariance are a bit shaky. Could you provide an example in another language that you think cannot be expressed using the provided utilities of most Lisp's?
You also mentioned that you don't think most Lisp's have "expressive" type systems. What do you mean by that? When I think of a type system as being expressive, I think of it as having explicit rather than implicit types, which is unrelated to the issue of strongly vs weakly typed and static vs dynamic types. Do you mean more like how you can describe / constrain the relationships between types in certain strongly typed languages?
Sure, agree. That's also the real point: type systems primarily exist to make semantically impossible computations unrepresentable in the language (at least, without some extra song and dance). To this end, Lisps have rather lackluster type systems, but they don't try to encode much of the languages semantics into a type system.
> My understanding of the terms covariance and contravariance are a bit shaky. Could you provide an example in another language that you think cannot be expressed using the provided utilities of most Lisp's?
The wikipedia article on the topic is a great source: https://en.wikipedia.org/wiki/Covariance_and_contravariance_...
I'm actually mostly concerned with type invariants (ex. List[int]), which don't really have much for representation in Lisps from what I've seen. Further, see above about using types to make compile-time assertions/checks about the behavior at runtime.
> You also mentioned that you don't think most Lisp's have "expressive" type systems. What do you mean by that? When I think of a type system as being expressive, I think of it as having explicit rather than implicit types, which is unrelated to the issue of strongly vs weakly typed and static vs dynamic types. Do you mean more like how you can describe / constrain the relationships between types in certain strongly typed languages?
"Expressive" is a nothing word that doesn't have concrete meaning in-context, similar to "strong" type system. That being said, I would say Rust, OCaml, and TypeScript have expressive type systems: the behavior of the language is largely encoded as types. The implicit vs explicit nature of types is not super consequential IMO, it has more to do with how you primarily represent semantic meaning. In lisp, it's symbols. In Rust, it's traits, enums, and structs (+ the affine types, but that's not relevant here).
That makes inheritance-based covariance and contravariance largely moot.
You have substitutability-based covariance and contravariance (you can't get away from those) but they cannot be subdued declaratively.
E.g. if you're passing a callback function somewhere, which you know will pass widget objects to the callback, it's okay to use a function that was written to handle gadget objects, if widget objects are designed to substitute for gadget objects.
That's contravariance of substitutability.
Substitutability is the only thing that matters in the end. Declared inheritance doesn't ipso facto guarantee substitutability, and therefore declared covariance or contravariance, which are inheritance based, do not guarantee actual substitutability-based covariance or contravariance.
Having an order of magnitude more unit tests is another option, but that undermines the alleged "less code" benefit of weak typing.
In [0] in particular there're slides 22/23, here is part of the transcript but makes more sense with the slides on:
> And you can call them problems, and I'm going to call them the problems of programming. And I've ordered them here [...] I've ordered them here in terms of severity. And severity manifests itself in a couple of ways. Most important, cost. What's the cost of getting this wrong? At the very top you have the domain complexity, about which you could do nothing. This is just the world. It's as complex as it is. > > But the very next level is the where we start programming, right? We look at the world and say, "I've got an idea about how this is and how it's supposed to be and how, you know, my program can be effective about addressing it". And the problem is, if you don't have a good idea about how the world is, or you can't map that well to a solution, everything downstream from that is going to fail. There's no surviving this misconception problem. And the cost of dealing with misconceptions is incredibly high. > >So this is 10x, a full order of magnitude reduction in (?) severity before we get to the set of problems I think are more in the domain of what programming languages can help with, right? And because you can read these they'll all going to come up in a second as I go through each one on some slide so I'm not going to read them all out right now. But importantly there's another break where we get to trivialisms of problems in programming. Like typos and just being inconsistent, like, you thought you're going to have a list of strings and you put a number in there. That happens, you know, people make those kinds of mistakes, they're pretty inexpensive.
[0] Video: https://www.youtube.com/watch?v=2V1FtfBDsLU
[1] Slides and transcript: https://github.com/matthiasn/talk-transcripts/blob/master/Hi...
[2] Video https://www.youtube.com/watch?v=YR5WdGrpoug
[3] Slides and transcript https://github.com/matthiasn/talk-transcripts/blob/master/Hi...
The reality is I never get to choose. I prefer strong typing. But the projects I am on that ship sailed long ago 2 developers back who used this project as a resume builder.
- Use notepad for development (don't laugh, I've actually encountered a dev who's choice IDE was notepad).
- Never worked with more than 1 person on a project.
- Never worked on anything but tiny projects.
- Never re-opened a project after having worked on a different project for several weeks.
TS has a very complex type system in exchange for relatively weak guarantees about correctness. That's a tradeoff you can choose to take or leave depending on the problem at hand, and holding that position is not the same as believing types have no value.
Unless you're talking about elm or rescript or something then sure sure. But usually when people say things like this they mean typescript.
But all of that makes plenty of sense because in the early days it was rare to work on a real life production JS project that was pure TS from day one in addition to having TS only libraries.
These days it's everywhere and the work has been done to move away from rampant anys in popular libraries. The tooling is also mature and it's rare to find major JS libraries not already packaged with types or a @types import. Turbo moving away from TS was the rare exception.
Well:
More than a second to compile a "hello, world" (or 2.4s in CPU time) is orders of magnitudes slower than any other compiler or interpreter that I know of. It's a ridiculous start-up time.esbuild is not an alternative as it doesn't check types.
What I want is "GET /foo.js" to "just" compile TS "on the fly" in dev; it's a simple "just works" kind of setup, but not possible with TS.
Or for a simple system, just "roll your own":
Doesn't need to be shell, can be a simple JS script or whatever. You really shouldn't need millions of lines to call a compiler even in simple scenarios.What exists now is an explosion of complexity to deal with all this and I guess these systems are "mature", kind of, but for a lot of systems it's massive overkill (and even for larger systems it's not particularly great IMO). Besides, it's really a bad fix for more fundamental problems.
Personally I wouldn't call TypeScript mature until compile times are roughly within the range of literally ever other compiler that has ever seen wide-spread adoption (and with that I don't mean "parallelize to 32 cores so my threadriper over 9000 can compile things in 0.1s). It doesn't need to be fast: just not a huge outlier.
Explosion of complexity and "bad fix for fundamental problems" sounds a lot like you just have an axe to grind.
Many other popular languages have multiple build systems and tools to choose from as well; it isn't a particularly novel challenge.
"You just have an axe to grind!"
Hmkay.
I just want things to compile, with errors, like literally everything else works. It's really not a huge ask. I don't want complex bespoke setups with multiple compilers and background processes and whatnot. The classic JS/TS response is "here is the happy path with all this tooling, but if you want something outside of that then there is something wrong with you".
I guess the "axe" that I'm "grinding" is that I'm having a lot of difficulty using TS in a way that fits with my sensibilities and preferences. e.g. I don't like errors in my editor and prefer to explicitly call the compiler (for any environment). I suppose I could get all of this to work how I want it to with wrapper scripts or whatnot: but it's complex, time-consuming, and isn't needed for anything else.
Based on previous conversations about this at least one person will say something along the lines of "zomg what kind of crusty old backend unix beard doesn't use VSCode and want errors in their editor, you just need to get with the times as you're stuck in the past!!!1" but again: it works for literally everything else (including other compile-to-JS tools), and is not that "obscure" of a work-flow, IMHO, and we're back to a few paragraphs ago: "move outside the happy path and you're screwed".
`tsc --noEmit a.ts` (possibly flipping the position of the flag, I forget if it is sensitive).
That'll check the types without spending unnecessary time compiling with the slower tooling.
From there, esbuild or swc binaries compile the code as desired. They use the same tsconfig file that tsc does, so no need for any extra complexity. They build so fast that you'll not mind having a separate command, I promise.
You could even combine the two into a simple one-liner if you wanted.
Compared to, say, java where you have to fight over maven or Gradle or ant, plus endless config options in XML or groovy or whatever, typescript really isn't much to complain about.
Hell, trying to set up a clojure full stack project is a nightmare of conflicting opinions over tooling between lein and shadowjs or whatever.
Of the big languages, C# is about the only one with a "one true way", and of the smaller ones, they simply haven't yet developed a big enough base to grow contentious enough to have divergent solutions.
The raw output speeds aren't a very good 'practical real world example' as the OP described it.
If anything in dev ESLint (+ Copilot) is the slow ones that I sometimes noticed, but there is already Rust driven replacements maturing in the pipeline as we speak.
I looked in to this some time ago, because I couldn't believe it was this slow, and tsc just has a huge startup cost. Once it gets going it's alright (I think? Don't quote me) but to get started takes a long time. It's actually already improved because not too long ago it was more like 3 seconds (probably because of the parallelisation, which is kind of cheating IMO).
I don't know about Java as I never really used it, but complex build systems are not unique to TS of course, but what they are in TS is mandatory for any reasonable experience because it works around the fact the compiler is so damn slow. That's a huge difference you can get started without too much effort, and you can do "smart" things fairly easily as I mentioned in my earlier comment.
> they simply haven't yet developed a big enough base to grow contentious enough to have divergent solutions.
C, C++, Go, Rust, Python, Ruby, PHP don't have a "big enough base"? Ehhh
And my entire point is that TS LACKS "divergent solutions". Because it's so slow lots of solutions are simply not practical.
Sure it can be an adventure to express something fully in it - but this is true for any type system I've seen.
And I haven't seen another type system that's integrated into a dynamic ecosystem as well (IMO python is 5 years behind in terms of type checking experience) and that lets you chose the level of type sophistication that makes sense.
Static typing, code analysis, automated testing, etc. are all great tools that become counterproductive past a certain point. Where that point is highly depends on what you're doing and typescript is one of the most flexible type systems at letting you make that choice. I'd say a lot of people are terrible at recognizing when they went too far with it for no practical gain.
My only problem with it is that they can't fix the shit JS semantics.
I'm just pointing out that having an opinion about typescript is not the same thing as having an opinion about types. Something I think people are (intentionally?) conflating in these comments.
Too many web developers, basically, I don’t think the conflation is intentional
I still have hopes that Kotlinjs can fix the distribution size and the tooling around binding to js libraries.
IMHO these are markedly different scenarios: the first is essentially just a difference of opinion, the second is a rather myopic attitude.
This applies to much more than static typing as well. Anything built, can be built well or it can be built quickly.
Although I often have to make trade-offs for immediate gain at work, there is a big difference between the quality of my work over time vs the quality of other devs who default to immediate returns. I will say in their defense, they play a role in the team. But I wouldn't want to work on a team where avoiding upfront costs was the expectation rather than the exception.
"I've worked with my fair share of vanilla JS devs who refuse to acknowledge the benefit of testing. Most of them bemoan the upfront cost of testing everything without realizing the benefits of it. I absolutely think less of them as developers."
The really issue with compile-to-Javascript languages is debugging. Have fun stepping through your incomprehensible generated code in dev tools.
(Except Dart which has really good Dev tools support; I guess they can poke the right people to make it work.)
So, how often is poorly tested code out there? From the popularity of strong static typing, it must be ubiquitous.
Static verification (what includes static types) is the real deal.
That said, WTF is there with people insisting that types must be either static or dynamic, and that dynamic types are useless?
Whoever said you can only pick one of the two approaches?
If a developer can jump into an unknown part of a codebase and quickly see that following a certain structure will automatically make their code work for them without needing to read all the code first and double checking if it's just a random convention versus a strict interface so they don't reinvent the wheel or build code that doesn't fit in with the existing structure, then that's worth a lot and something you cannot simply cover with tests.
If anyone can think of something a unit test could test for that an arbitrarily* complex/strong type system couldn't I'd be interested to hear it. It's possible, I just can't think of any.
An arbitrarily complex type system, with dependent types, could detect any bug, since it's formally equivalent to requiring the program be proved correct. But using such a type system is so onerous that I don't know anyone who realistically does it in production.
If you wrote such types, you're basically giving a formal specification of what the program is supposed to do. That could then be used, and likely much more easily, for high volume property-based testing.
More info here: https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspon...
if you have a type system expressive enough to do things like that, you've basically got another turing-complete layer on top of your existing language, which is itself ... dynamically typed.
So, how often is poorly typed code out there? From the popularity of test coverage tools, it must be ubiquitous.
In a situation where extreme levels of testing occurs, the extra assurance from strong typing is minimal. In a situation where strong typing is enforced, extra testing is still very useful.
Consider a finite state machine with N states. There are a priori NxN possible transitions. In many cases however most of them are impossible, and you really have only O(N) feasible transitions.
Sure if you make O(N^2) tests you don't need typing... but typing could have made many of those transitions literally impossible. You don't need to test the impossible. For a sufficiently large codebase, you want to limit as much as possible the amount of tests you need.
Also I have more than once identified issues in our testing framework because when I made stronger types I uncovered bugs that escaped our tests. Non-deterministic concurrency bugs are extremely hard to test for. So yes, testing can uncover type bugs, but typing can uncover untestable bugs.
Ok, but it's cheaper to not have to write those tests than it is to write those tests.
That's quite clearly not what I'm saying.
It is like the things you already trust when you code. Let's say, that the file system works, that the network stack of your OS works, or that the cpu works. You can rely on them to use your time for the things that matter. This is a much better model than simply testing everything yourself since, as the tests number increase, any change to the codebase involves more and more test changes.
> Strong static typing is useful in the environment where the code is not adequately tested. That's because tests adequate to make the code bulletproof will also detect the problems strong static typing could detect, rendering SST superfluous.
Your logic is the same as follows: seat belts are useful in an environment where drivers are not driving adequately. That's because adequate drivers will drive in a way to avoid any danger that the seat belt would prevent, rendering seat belts superfluous.
Ultimately, the problem is adequate drivers (as in described above) or bulletproof tests do not exist, they are just a concept. A test can prove the presence of an error, not the lack of errors, and the argument only works in absolutes.
The analogy with driving doesn't make much sense. After all, accidents sometimes happen without the driver being to blame, and the marginal cost of putting on a seat belt is very low.
Apart from an analogy is a template of your argument. It is the logic of the argument itself.
Errors also happen sometimes without type systems being to blame (they only catch a subset of errors).
The marginal costs of a type system is usually very low too, so it seems to be a great fit.
When you use a typed language, you can use your integration tests to actually verify business logic, exception handling, etc. instead of having to write a dozen test cases to make sure that doThing(table, index,*kwargs) doesn't blow up when 'table' is a list or 'index' is bytes...
(It won't find latent bugs that can't currently be exercised, so the testing can't be one and done.)
if you use a static type system you can guarantee there will be no type errors at runtime. why on earth wouldn't you choose that? you can still write logic tests!
and when you leverage the type system to make illegal states unrepresentable, you can make certain classes of logic error impossible as well. some of your tests become tautologies and you can delete them.
What testing cannot find are latent errors not exercised by the program. Do I care about these, though? Arguably these would be found by testing internal interfaces and elimination of code not reachable by tests.
The general argument I am trying to make is that the marginal value obtained by strong static typing declines as testing increases, and that in the limit goes to zero. If a program is adequately tested, is it still worth doing? This is not clear to me, and the arguments given here have not convincingly demonstrated that it is worth it.
Also: if you find yourself in a situation where strong static typing seems useful, you should be alarmed. It means you aren't testing your code very thoroughly.
I am not assuming that.
honest question: what language do you have in mind when you think "static types"? your perspective is so starkly different to mine, you seem to be operating under totally different assumptions about what types can and can't do.
I mean this sentence:
>Also: if you find yourself in a situation where strong static typing seems useful, you should be alarmed. It means you aren't testing your code very thoroughly.
is just baffling to me. it's so self-evidently absurd that I can't even argue against it. what is there even to say?
have you even used a modern statically typed language, with type inference, generics, null safety, algebraic data types, pattern matching, etc? I cannot imagine trying to maintain a big codebase without them. they don't slow me down, they speed me up. they aren't just about catching trivial int-instead-of-string bugs, they are are core tool for modelling data and business logic. they let me define problems out of existence (see e.g. this series of posts https://fsharpforfunandprofit.com/posts/designing-with-types... for an introduction).
and then there's rust and newer-generation languages with borrow checkers and affine/linear types, ruling out entire classes of memory and concurrency bugs ... your test suite cannot rule out data races, but rustc sure can.
you are making the same arguments people were making in the 2000s when most static languages sucked because they didn't have these features. I don't want to go back to a time before sum types.
>What testing cannot find are latent errors not exercised by the program. Do I care about these, though?
you should, because in production your program must endure orders of magnitude more variety in inputs, uptime, and runtime conditions than the test suite can exercise. it can and will get into weird states you didn't anticipate. yes, you can fuzz, yes you can property test, I know all about that. those things are good. but I don't get why you wouldn't also use a static type system to provably rule out classes of problem across all possible code paths. why settle for less?
Because type errors in unit tested code are basically non existent. It's a fictional problem and as such there is no point in spending real resources chasing fictional problems.
There is plenty of research that shows that static typing gives no benefits (statistically insignificant) when it comes to software correctness.
What you are asking is why shouldn't the local government spend money on a Yeti patrol to protect the general public against Yeti's? The Yeti patrol will eliminate an entire class of problem (Yeti attacks).
this is short-sighted. I am not talking about trivial string-instead-of-an-integer errors here. powerful type systems let you encode far more sophisticated constraints on the program. safe rust makes data races into a compile-time error via its type system, for example. unit tests can't do that.
I think you're greatly underestimating what types can do for you. you seem to have this mental model where you would write the exact same kind code with a type checker as without one, and the only difference is whether you have to convince some pedantic bureuacrat that your code is correct when you already know it is.
but when you have a powerful type system, you don't write the same kind of code. the type systems helps drive design, similar to how tests can drive design. you have probably seen code that is bad because it wasn't written with testing in mind. there wouldn't be much value in adding unit tests to the code right away -- you probably need to do some highly invasive re-architecting to make it testable.
so, is it really such a stretch of the imagination that code can also be deficient because it's untypeable? perhaps the reason you don't see much benefit to types is because you didn't write the code with types in mind, as a design tool.
there is a learning curve to writing testable code. the same is true for types.
read this if you haven't, about type-driven design: https://fsharpforfunandprofit.com/series/designing-with-type...
No, my mental model is removing the type checker allows me to write shorter, more concise and higher quality code. Dynamic typing enables much better code styles than static typing.
> the type systems helps drive design
Ahh you are an complexity merchant, if only I made my code more complicated all my problems would be solved. I'm afraid not, the more complicated your code the worse it is.
No I'm afraid, I have actual real commercial experience of doing both styles of software development, the dynamically typed code is the better approach by far.
It's not that I don't understand you, it's just what you are saying is a load of rubbish. :p
> We should not categorically denounce people who prefer it as lesser developers
I don't think you've justified this point. I'm comfortable with my position on this.
It seems reasonable to argue that statements that you won’t even discuss X any more and would rather judge the person as less competent professionally are unproductive. Not saying I agree, btw. But it does seem like a pretty basic point. One of you is talking about preserving your sanity and the other about output. It’s not necessarily a disagreement even.
That is absolutely NOT what the other poster said. They said they _MAY_ judge them that way.
I still think it's reasonable to argue that's not a "productive" approach, but like I said, that's not my own opinion, I just think it's a reasonable argument.
I don't necessarily think it makes someone a "lesser developer" if they prefer untyped languages, but I DO think they've either had to maintain anything long term or it stayed relatively small.
Whether that makes them lesser or not isn't really for me to say, but I can say I'm definitely on board with the idea that types increase productivity the longer a system is maintained. Unless used poorly, types don't automatically mean you use them well, but they make it a hell of a lot easier to do the right thing.
Some issues are settled enough that there is no need to discus them any further. It is okay to automatically mark people on the wrong side of such issues… let’s say ill-informed.
Static typing, I believe, is close to being one of those issues.
Imagine someone is working in a relatively niche new programming language ecosystem which is dynamically typed, allowing the language to have some richness that modern type systems don't support. I don't have an example because I don't know of any such language in 2023... BUT back in the 70s and 80s this would have been Lisp. Lisp couldn't have been strictly typed back then because, AFAICT, type systems hadn't advanced enough to express the sort of metaprogramming that made Lisp unique and awesome. This was at a time when most popular languages were strictly typed.
I would hope that you would keep your mind open to whatever that maps to in the 2020s.
Now, if someone says "I like JS over TS because types are annoying and slow me down", then yeah, I don't have much patience for that either.
The thing is, I've yet to encounter a single instance of such an argument today. Every single time it ends up being "I like JS over TS because types are annoying and slow me down". It hadn't even occurred to me that laziness and sloppiness weren't the the only reasons to write in dynamically typed language.
I suppose what I'm saying is I'm quite interested in seeing what kind of evolution some dynamically typed language could offer in the future. Although with no signs of its coming, I'm going to stick to TS because it's objectively better for anything but very small projects.
At best it's a form of documentation that enables some simple linting rules and a bit of jump-to-definition magic. Like, that's a benefit (mostly -- I tend to think that type signatures implying more guarantees than they provide is a recipe for inadvertently relying on falsehoods), but it's not as clear-cut of a win as you see in other statics/dynamic tradeoffs.
I don't mean to be unnecessarily contentious, but isn't that the point of undiscovered territory?
We haven't encountered it yet, when we do then as awareness grows that pattern, idiom, theorem or concept will be picked up by mainstream statically typed languages.
It's enough to believe that the properties of (for example) JavaScript might lead to an as yet undiscovered pattern that is not possible in current statically typed languages.
Unlikely, but still possible.
The type system enforces those permissions: writing to an impermissible destination is a type error. The types applicable to an entity are not necessarily knowable at compile time; some of them might change at any time.
I had a job working on such a system. It supported a type system implemented in Haskell with both static and dynamic type disciplines, where values were tagged with base types designed to be checked dynamically in hardware.
Was the programming language dynamically typed? Yes. Was the programming language statically typed? Yes.
You can't honestly believe no one who likes to develop in a dynamically typed language has nothing interesting to say about software development?
But that wouldn’t be a wise use of time in my opinion. And that’s what they’re ultimately driving at: we all have limited time to expend, so do it in things that matter to you. I believe “matters to you” is a bias.
Beliefs inform decisions and other beliefs
I disagree with them on a huge number of fundamental things in software. Most of their fundaments are claims with no evidence. Due to that, I simply do not care about what they have to say most of the time.
It is both useful and allowed to have a certain level of belief that won’t be crossed without heavy effort. The OP didn’t even say they were closed to the conversation completely, but that a random person with no pre built trust isn’t going to get the time of day from them to rehash the same settled argument.
from memory Matz -> ruby, Valim-> elixir, Van Rossum -> python, and 'the creators of Julia' -> julia
Not 'random persons' then
This confused me for a moment, but I think you mean annotating the types of the fields in your `struct`s, right?
An addition to that: any non-constant global variables (if you must have those) should also be type annotated.
Dynamic types are nice for quick & short scripts, and actively detrimental for anything long and complex. Why not enforce that? As soon as it gets so long it doesn't run you know it's time to rewrite in a statically typed language. Instead all existing scripting languages allow unlimited growth in code size, making it easy for programs to grow beyond the point where the language is useful.
A long list of big products and companies are there to disagree with this. I am not saying that dynamic is better or worse. But saying that dynamic languages are only good for quick short scripts is a wrong generalization that has not one exception but many exceptions.
But people regularly say the sky is blue, and it's clear as day that it's not when the earth has turned and your side isn't facing the sun.
Only due to Rayleigh scattering do we perceive it as blue, but that’s not due to the absorption and reflection of different wavelengths we associate with innate color. Note that the color changes depending on the angle of the sun, even to the point that it’s purple and red at a few times during the day.
Our perception of the important colors (sky blue, ocean blue, vegetation green, …) probably evolved along with our physical needs.
Don't venture into tornado country; your doubt may be your downfall.
But he's just being honest. Many developers have been making that judgement for many months or even years.
For the very rare cases where a lack of strong typing is needed, most languages with strong typing offer ways to handle that (i.e. the Object and Dynamic types in C#)
It's like if you met a builder who refused to use a hammer and insisted on bashing nails in with the back of their drill. It's not "personal" to say you'd respect that person less as a builder, regardless of how much you'd enjoy having a drink with them.
I'm currently responsible for a very large system built in raw javascript where function definitions like this one in the article: function birthdayGreeting1(...params)
...are the religion.
It's awful. I hold the people responsible in very little regard.
That's a pretty strong take! I don't think there are many things that I feel similarly about... maybe if someone suggested that they don't need test environments and can just deploy changes to prod without testing or CI/CD and just see what happens, when it'd be my employment on the line, but even that's a pretty contrived and out there example.
> The amount of pre-existing respect for someone I'd need to have before I engage in a good-faith discussion on "are types good" is pretty high.
My problem is that not all type systems and the way you use them are equal.
When working with back end code, I really like .NET or even Java having a type system there for me. I know that people suggest that they have their own shortcomings (type erasure, NPEs, no multiple inheritance, even smaller things like C# enums not supporting methods) and that there are better options out there, but generally you can turn off the part of your brain that'd worry about the language too much and just deal with the domain problem at hand. Something like JetBrains are also excellent, because with the type system suddenly the tool also can reason about the language constructs you're using and give you all sorts of good suggestions and refactoring options.
Whereas with something like TypeScript in combination with React, there are times where you fight the type system instead. That's just the impression that I got working on a few projects for a while, in comparison to React with JS (perhaps the code was also a bit too clever), while with Angular it felt more coherent to me (despite Angular being more complex otherwise and not really my first choice). In the end, I gravitate towards Vue with JS for my personal stuff, but it's not like you can just say no to TypeScript when you need to maintain something long term.
I can't actually remember who said that they ditched TypeScript for similar reasons, but the argument was basically that a non-insignificant part of their codebase was there just to satisfy the type system. TypeScript does what it's supposed to... but it feels like it could be easier.
Not even one of my spicier takes, just one of the few that I simply don't care to engage with further.
> My problem is that not all type systems and the way you use them are equal.
We agree. Some type systems suck so badly that I can see why people would be tempted to believe that all type systems suck.
But that's my point: people say that exact thing about Java and .NET, while I find them usable. Meanwhile TypeScript has cool stuff like union types and other stuff to the point where you can get pretty clever with it (https://codegolf.stackexchange.com/questions/237784/tips-for...), which many would describe as the type system being objectively better, yet it's also more difficult for me to use.
In my mind, a good type system would let you do both basic stuff easily without too much work (to make sure that refactoring doesn't make you shoot yourself in the foot) and also encourage you to write the simplest code that you can get away with, while allowing you to get clever in the select few places where that is actually needed.
Which is funny, because adjacent to that, Java and .NET (web) frameworks can be a masterclass in incidental complexity, even though for me the type systems don't get in the way too much.
Edit: actually, I think I'll migrate a JS project to TS, this time in Vue. Perhaps Vue 3 will be a pleasant experience and if it won't, then I'll have a concrete list of things that caused me to feel this way.
That's pretty much my take. I don't hate anyone for having different ideas about it. But if you're going to use Typescript, don't use 'any'. At all. Unless you really don't know and the next step is figuring out what type you've got.
That's actually not hard, you need to be able to run in dry mode, and run the same in an existing instance (in parallel), then compare the results. If you are happy you can continue with the roll up, disabling the dry mode.
> edit: To clarify, I am on the "types good" side of things
I agree. Python 1 would have been worthless if it didn't have types.
Dynamic typing also makes a ton of optimizations basically impossible and even after monumental efforts languages like Javascript are still quite slow, inconsistent and memory inefficient outside of trivial benchmarks.
I thought this meme was dead already. Of course, you might not be able to squeeze out the same amount of performance compared to a brilliantly written C or Rust program, but for what it is, JavaScript is pretty damn fast already.
DOM manipulation on the other hand, is still a very common bottleneck people come across when writing typical JavaScript code.
It's fast compared to other dynamically typed language implementations but it's still very slow compared to basically all of the popular statically typed languages.
Notice that in the Java, C#, Go, Rust, Swift or Ocaml benchmarks almost all of the underlying data structures and much of the networking stack are built in the respective language. This is not possible with Javascript, Python, Ruby etc. because it would be ludicrously slow and extremely memory inefficient.
true
> it's still very slow compared to basically all of the popular statically typed languages.
Not true.
The main slowdown for javascript (AFAIK) is the checks the optimizer has to put into place to ensure the assumptions it's made about the type are still valid. If, however, those assumptions are valid then javascript ends up emitting pretty much the same assembly that you'd see for and highly optimized statically typed language. In fact, there are some circumstances where it can beat a language like C++ or Rust due to the fact that it has to incorporate runtime information into optimizations.
With C++ or rust, if you add dynamic dispatch, unless you are doing PGO and whole program optimization, you are pretty much sunk with 2 memory lookups on every function call. This is the case where javascript can end up beating C++/Rust.
(All of this is talking about hot code after warmup. During the initial execution javascript will almost certainly always be slower).
Part of the proof of this was asm.js, the precursor to wasm. V8 at the time it was introduced could execute asm.js nearly as fast as what firefox could do with it's optimized asm.js compiler. That is, when you stripe out all the actions that make javascript slow, it very often ends up being just as fast as a compiled language.
What stuff ends up making it slow? Generally speaking, stuff that makes the types unpredictable (adding fields, removing fields, sending in a number and a string and expecting the VM to be able to handle both).
You can see a lot of this writeup around the discussions about why Dart was originally "optionally typed". Basically, the entire selling point to make dart fast was simply to remove the abilities to dynamically change types like you have in javascript. With that, the VM authors at the time were capable of making a VM that's every bit as fast as what Java has.
GCC at least is capable of speculative devirtualization by using local heuristics, without PGO. And of course it is capable of devirtualizing in many cases when the knowledge actual type can be constant-propagated.
Also note that the vast majority of calls are not dynamic in C++ (as opposed to most dynamic languages), so devirtualization is significantly less impactful.
ok...
> In fact, there are some circumstances where it can beat a language like C++ or Rust due to the fact that it has to incorporate runtime information into optimizations.
People used to make the same claim about Java and in every single example I've ever seen the Java/Javascript is extremely optimized, performance isn't consistent across VM versions, and the C++/Rust is extremely naive (usually allocating unnecessarily and not using arenas in hot paths that are allocation heavy).
It never happened.
And, you can look at the code yourself, most of the examples read pretty much exactly the same as their C++ counterparts.
Mind you, this is also a test that looks at execution start to finish and doesn't give warmup time (which will always favor statically compiled languages).
> performance isn't consistent across VM versions
That's true for C++ compilers, so why would you expect performance to remain constant with a JIT compiler?
[1] https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
There's a certain segment of the developer population that I don't think realizes just how fast C and C++ are. Javascript is _relatively_ fast when compared to other dynamic languages, but not when compared to C, C++, FORTRAN, etc.
I write Clojure in my day job and it’s insane how often we have issues where it would have been immediately caught by a static type check.
How have you experienced using Type Clojure, spec, Malli, etc. to determine correctness?
I've only worked on solo projects with Clojure, with most of it fitting into my head. I imagine with teams of size N > 1 things can change quite a bit.
Can you describe your usecase for which JavaScript is slow? There are many languages that are slower than js like python or elixir, but they are doing just fine, that's why I won't agree that js is slow, but sure there are cases for which js just wasn't designed, and any CPU intensive task will be slow, but there are ways to get around it as well.
Please provide evidence for this extreme claim. I can point to benchmarks[1] where JavaScript is competitive with or even better than compiled static-typed languages like Java.
(any argument that those are "trivial benchmarks" is automatically invalid without actual empirical evidence)
[1] https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Nowadays, the static typing is much nicer, with both higher benefits and lower costs, and it makes the costs/benefits analysis much more likely to come out in favor of static types.
In the late 1990s when I was cutting my teeth, I did a lot of Python, and I was almost 100% dynamic language until ~2015. In hindsight, I might do the same again even if thrust back in time. There just isn't a great static option back then. (I'm not saying 2015 is the year it became practical, I'm saying that's when I finally moved into static languages. 2010-2015 or so I was in Erlang, doing things that most other languages couldn't do at the time, so that forced me into a dynamic language. C# was looking pretty good in that time frame too, it just wouldn't have run the systems I had on anything like the resources I had at the time. It could probably easily do it in 2023 though.)
Now I even prototype in static systems, and it's a better experience than prototyping in Python was. Like, by quite a lot, honestly. In the end, I don't find it that much of an impediment to make sure that if I want to call a method on a thing, that the method actually exists.
Dynamic typing will never disappear; there's a certain small size of task for which it'll always be more advantageous than static typing, and while said tasks may be small, there's a lot more of them than there are large tasks, so it's a completely valid and sizable niche. But I do think over the next 10-20 years we're going to see the "scripting" languages return back to "scripting" and away from "systems".
I think that in the end, the dynamic scripting languages being used for large tasks will be seen as a reaction to a misdiagnosis of the problems in the 1990s. The code was atrocious in the 1990s not because it was statically typed, and therefore the solution is to go dynamically typed. The code was atrocious in the 1990s because it was poorly statically typed, and the solution was to get better. That said, "getting better" did take a long time, and for many legitimate reasons.
(Much ink is spilled on the so-called rapid pace of technological innovation in our industry, but programming languages move on decadal scales. Programming languages still have only barely grappled with a multicore world, and haven't grappled with a heterogenous computing world at all (GPUs on one end, efficiency cores on the other). Things are not always in as much motion as we fancy.)
[1]: Particularly, Hungarian notation is supposed to supplement the type, not just reiterate it. If you're in a language where you can't easily declare "a width is an int", then Hungarian notation suggests calling a width variable something like "wdthDialog", so that you stand a chance of noticing that you passed a "hghtDialog" in the wrong place. But the way it was used a lot of the time is you got "u16Width" instead, where u16 meant unsigned 16 bit int... but that's already in the type. Using it that way just adds an extra layer of hierglyphicness to the already ugly code. One of the several innovations that made static typing languages more feasible is that in most languages designed in the last couple of decades, you can declare something like this with something like "type Width int", and then you don't need to label any variables with it at all, the compiler enforces it.
1. It's simple. Let's not lie, let's not pretend. It's CRUD. You can put in K8S, you can add AI, it can be behind an API Gateway, you can make it all Event Driven, you can use CQRS for every entity because you really, really want to feel clever. But it's still CRUD.
2. Other people are working on it, many other people.
3. People from other companies are interfacing with it.
So knowing what to expect trumps everything. Types help with that. They help a lot.
To be clear: people have tried to show the long-claimed safety benefits for a long time, and they just refuse to appear.
What have you built or added to the field that justifies such a lack of tolerance of those who don’t subscribe to your point of view?
That said, yes, of course my post is arrogant and dismissive. I am outright saying I won't engage in a conversation. I'm comfortable with that.
It's actually arrogant and dismissive because you say that in a way clearly intended to spark a conversation.
Also, pretty much all arguments for explicit typing suffer from mechanistic bias (see, https://www.youtube.com/watch?v=NmJsCaQTXiE&t=143s). I'm comfortable saying I just enjoy tinkering with a formal description of a data structure, because I'm a type enjoyer, not a type fan.
I don't follow at all. Why would it be arrogant or dismissive to say something in an attempt to discuss it? I commented in good faith to an article that has a strong opinion ("willing to die on this hill") with a reflection of my own strong opinion ("unwilling to discuss").
At most you could say it was inflammatory, which wasn't intentional although looking at the insane number of child comments it apparently ways.
Types are integral part of standard programming languages. If you mean "types bad" as in BASIC/Javascript variables, then yes I fully agree. These languages were never meant to be a solution for professional software engineering.
Once a language has proper types, it can be "weak typed" by not forcing "strong typing" by being dynamically typed in nature. If the language has normal preprocessor support, the static type system can be added on the project level. And then in essence you get a strong typed environment in a very thin programming language such as C.
Let's take a big C or C++ software project, the process behind it. Of course, the project is in those languages because it requires native opaque pointers and hardware access. The project has coding style, it has arbitrary rules. Although there is little to stop anyone from making a mess in C or C++ code there is entire code infrastructure and CI/CD chain around it. Lets say the rule is no void pointers without encompassing struct that's a type that can be checked via macro system. If you wrongly use the type struct, you get stopped by the project build. If you go around the rule, you get stopped by a code analysis after commit (and get in trouble for doing that).
I think that says more about the strength of your zeal than of your argument.
if you get the static type system wrong once, there's no going back
Take for example, Rust: mut, Send, Copy, Drop, Debug, dyn have all their warts because they are special and you can't fix them because you'll break previous code.
In a gradual typing language you could potentially bolt-on your own type system as a package where it just runs the type checker if you want one. It just so happens people who favor these gradual typing systems don't do a good job of creating those typing systems because they are not that into types in the first place.
But in theory, this kind of a system would let you not worry about types when prototyping the system, then put some bounds later based on your requirements.
SML or Haskell? Yep, like those ones. The languages would not be so useful without their compile time type checking.
C? Not worth the trouble. C++? Not worth the tarpit or the trouble.
Python? Definitely not keen, that language was much better without the annotations.
Typescript people really like but I haven't played with. I'm pleasantly surprised that unsound static + sound dynamic works well.
Type annotations are not inherently good. Some type systems add a lot of value, some really don't.
lots of comments here praising Haskell and Rust frankly seem to be coming from people with a superficial understanding or limited exposure
I've had rustc complain about type deductions that were over a line long...that's just one inferred type...you can easily paint yourself into a corner with a type system with no exit other than trying to cajole the right definition out of the compiler and then you copy-paste it and pray
The OP said strong static type system, so C and C++ are out of the question.
People who say they prefer dynamic languages are really just saying ‘no’ to all these free benefits.
Otherwise the cost is merely being unable to write anything that the type checker does not understand, and however long your compiler takes to do the checks on what it does understand.
Also a fair chance the whole program must type check before you can see the results of changing a subset. In the worst case you get to hunt down all the unused variables before it'll run the test suite.
I prefer dynamic languages. That makes me a heathen on these boards, to be ignored or chastised for my stupidity. Regardless, those benefits do not come for free.
- having a compile step in between writing and running your code. This drastically slows down the feedback loop of development. The more your compiler has to check for you, the slower it gets.
- being able to run your program in a half-broken state. This may not seem like much of an advantage, but sometimes it is good to just be able to run a broken piece of code to see how it crashes. This is especially important when learning to program, but is still very beneficial when learning a new language or framework, or sometimes for debugging.
- As someone who has contributed to the main Haskell compiler, I can definitely confirm that a more complicated type system can slow down development of the language itself too. It takes significantly more effort to grok all the possible interactions as the codebase of the compiler grows.
Don't get me wrong, I think encoding and enforcing program properties with types is a great idea that will grow further in the future. But the dynamic languages gained popularity for good reasons, and some of those reasons are still valid today.
Not strong enough.
If you are proponent of strong typing, you need something that is effectively a theorem prover. I.e when you define a type, you define the scope of the data it can hold, operations on that data, and the resultant types of those operations. That way, when you code compiles, it is by definition "correct".
When you accept anything less then that, you are basically making a statement that you are willing to forgo some of that correctness for convenience, which is fine, but that means that no language out there is really good or bad.
That said, your position is also just weird to me. Yes, theorem provers have nice type systems that are wonderfully expressive. The trade-off is that some common programming patterns become difficult to use or are even impossible.
When it comes to everyday programming, I don't think theorem provers are at a point where they are particularly useful. Not everything needs to be proved formally, and I don't think this position is at odds with the belief that static type systems are generally "better" than dynamic ones.
Not really. Strong and expressive typing at its core is simply creating data packaging containers that have defined operations on them. It says nothing about logic. You may be referring to the functional programming aspect that comes with strong typed languages, which is related but not the same as strong typing.
The point is that typing is just a tool that a programer can use, whether its built into the language or ran statically like MyPy. You can take a piece of code in C, and write a test suite on input and output of that piece of code, and accomplish much of the same thing that typing accomplishes. However, in the case of strict+explicit typing, the idea is that you wouldn't need tests in the first place, because your code would be correct by nature of compilation.
Some theorem provers, such as Coq, are not Turing-complete. This means you cannot write some programs, and in particular you cannot write infinite loops in Coq. Infinite loops are a common pattern (e.g., a REPL or a GUI display waiting for input).
This is a trade-off, as I said. You gain the expressive type system, but lose the ability to implement certain programming patterns. It has nothing to do with the strength of the type system.
---
> whether its built into the language or ran statically like MyPy.
All (true) type-checking is static, so MyPy "running statically" is not a noteworthy feature to distinguish it from other type checkers. I guess this point may seem trivial to some, but the broader context of this conversation involves conflation of the terms "static type system" and "strong type system", so your misuse of the word "statically" here seems worth pointing out.
That said, whether you use an external tool to check your types or the type-checker is built into the compiler is irrelevant, and I'm not sure why you brought it up at all.
---
> You can take a piece of code in C, and write a test suite on input and output of that piece of code, and accomplish much of the same thing that typing accomplishes.
Depending on the perspective, this is factually incorrect.
Type-checking is an ahead-of-time operation that guarantees the absence of certain classes of errors at run-time. Writing tests to check for the absence of such errors is not equivalent, because you have not proved anything; you merely gain confidence. They are semantically distinct, even if you write many tests to gain a lot of confidence.
You are talking about languages, Im talking about the concept. The modern theorem provers aren't up to the task. Due to Rices theorem, you cannot "fully prove" a program, so a language that does this couldn't even exist. The strict typing however can be applied to subsets of the programming space, namely the data processing pipeline, whereas higher level stuff like the actual server code that does have an infinite loop to listen to requests can be written in whatever.
The point is that no language recommended for strong type safety today is anywhere fully complete to include the rigor of something like a theorem prover, and anything less then that is basically your own opinion on what is "good enough".
>Type-checking is an ahead-of-time operation that guarantees the absence of certain classes of errors at run-time.
Run time errors are no different than compile time errors as far as testing is concerned. Its not like the computer blows up when you have a seg fault. And you absolutely can prove what you need for operation.
Say your input to your code is an HTTP request of length x. And output is some data processing on that request. You can write a test suite that is basically like this
1. Ensure that code returns a well defined error for x values outside of given range.
2. For all valid ranges in x, test all possible values of every byte in that range, and ensure correct behaviour.
While overkill, this will absolutely exercise every single piece of your code and prove correctness. You can also couple this with checking things like memory access
if I store sql with a column definition that stipulates that the value must be an int between 1 and 3...what difference does it make if I accidentally create a corresponding variable with the value "fred"? it will never be persisted
furthermore, you can run in to even more confusion with a language type that is not properly aligned with the database type...which one wins? the db obviously, since language values not aligned with database definitions will never be persisted
I'm not going to argue that these features are only possible because of dynamic typing, but regardless, statically typed languages tend not to have them.
Elixir may get "some form" of typing, but it likely won't be traditional static typing.
For Elixir, the concurrency model and supervisor trees. It's a perfect fit for some problems, and in general as a small company, the projects we use Elixir on greatly simplifies our production environment which is always a win.
Most of my focus when designing a project is to enable people with less experience than me to contribute in a bug-free fashion in the face of concurrency and parallelism. Sometimes that involves picking a funny language, sometimes it doesn't.
I've been writing code for 40 years, I can't estimate how many loc I've written, but likely over a million. One of my personal projects is currently over 65k loc vanilla js, and I never once had a problem with not knowing what type a function took. If you're so bad at naming things and knowing what a function does, I guess maybe types can help you. But not everyone needs it.
I think such a debate is largely unproductive. Like anything else, types have value (ha) and successfully capitalizing on that value depends on the context of the project, which includes things like developer experience, tooling, project complexity, requirements, deadlines etc.
The only productive outcome of these debates is that each developer gets to slowly and frustratingly build a list of pros and cons as they go through the arguments presented by either side debating this topic. In addition, developers who completely disregard either the cons or the pros are necessarily making subjective decisions with incomplete data, and the project gets to pay the price. Just because the developer is personally OK with all of their projects paying that price, doesn't mean it's the best decision for a project.
My experience has been that when starting out, projects get the most value out of not having types, and as they grow in scope and size, and the cons of not having types start creeping up, that's the point when gradually transitioning the code to being strongly typed allows the project to maintain its velocity _and_ quality.
Many years ago I thought that it's a good idea for a game math library to have separate strong types for 'point' (a location in 3D space) and 'vector' (a direction and magnitude in 3D space), and allow/disallow certain operations (e.g. 'point + vector => point' 'vector + vector => vector', 'point - point => vector', while 'point + point' is illegal).
Sounds absolutely great in theory, but in practice it was a royal PITA to work with, but it took me much too long to realize this (how can it be such a hassle when in theory it's such a good idea!)
I soon went back to a general 4D vector class (where a 'point' is defined by .w = 1.0, and a 'vector' by .w = 0.0), and some debug-mode runtime validation (which catches things like trying to add a point to a point).
Of course strong typing also often makes perfect sense, for instance in a 3D rendering API it should be a compilation error to provide a texture-handle where a buffer-handle is expected, but after this experience with points vs vectors (which should've been a classic showcase for strong typing) I would never again "die on that hill" :)
TL;DR: static typing: yes! strong typing: it depends.
Those help you more if you do a lot of math involving both them, but that’s also when it becomes a nightmare in most programming languages because of an explosion in the number of types.
Let’s say your code computes
If so, you need a “km hour” type.In many languages, you also have to write code to make that happen, and to make it have the same type as
Even if you don’t ever store values with those types in variables, you also may need types for per km, per hour, km² and hour² for expressing the types of intermediate values (for example, in a physics computation, you may encounter √(3km²/4hour² to compute a velocity in km/hour)And that’s ignoring that you may
- encounter minutes, meters, yards, etc.
- want to use algorithms that compute exp(3km) or log(4hours). What types do these have? Here, you probably want to forget about string typing values.
The good thing about stricter typing systems is that it forces you to handle all the cases when you're doing a refactor like this before it compiles.
When changes to the data model happen in large codebases of dynamic code, it frequently gets shipped in a non complete state and turns into a production runtime error later down the line.
cries at the thought of insanitybit not respecting me
The feeling is mutual.
discussions are a two-way street. if you aren't getting it, then how do you expect someone else to "get" your position?
-- Bertrand Russell[edit: To clarify, i am on the "i like strong static typing most of the time but also understand their pros/cons wrt. dynamic languages" side of things]
I probably love assembly language more than C#, or C++, or any of the strongly typed languages I use.
Assembly language has no types. It doesn't pretend types even exist.
Are you going to look down on me because I like to write assembly language?
Nope.
Say you can do the project in 2 ways.
First way is to use a strongly typed language, think about the data, and design your code in the appropriate way. You code it up, go through the loop of compiling and fixing errors, and get your code to run.
The second way is to write your code in Python, without worrying about strong types. You complete the code quite a bit faster, but since you also want your code to be correct, you spend time writing an end to end test suit for your code.
The second approach is not only faster (since you are writing tests in both cases), its overall better. Spending time writing tests allows you to essentially validate things that modern mainstream strongly typed languages can't catch at compile time (for example, what happens when the input is unicode strings?). It also forces you to think about end to end behavior and making sure that is correct, rather than just the behavior within your code.
Strong typing is a simply hand-holding tool for programmers. If you cannot write correct code without it, you are on a fast track to being replaced by AI eventually. The future of programming is not going to be designing data structures and types, its going to be using English to generate large chunks of code in most likely Python, and then tweak fine details in those.
I honestly don’t know if we’ll still program by hand or not, but I do look skeptical at people who are very certain we won’t. Don’t think that’s the first time in history people make that prediction…
As far as adoption, there is a reason why dynamic type languages that feature a lot more natural syntax (Node, Python) are used WAY more than others.
also it's pretty ridiculous to say "static type systems are handholding" and then say the remedy is to write your code with AI ..
This is, in fact, MUCH easier to do with a strong test suite that only cares about input and output rather than internals.
The most common approach to starting a refactoring project is write or enhance a test suite to the point where there is no undefined behavior, either the program works or handles the appropriate errors.
You don't need to aim for 100% test coverage either, you just need your tests to cover all possible inputs (including fuzzing).
There's only explicit typing and implicit typing.
So the only real argument you're having with someone is when writing a piece of code, do they want the caller of the code or the input of the code's data type to be known or they want the data type to be a mystery to be figured out, occasionally in production when shit hits the fan.
> That said, yes, of course my post is arrogant and dismissive. I am outright saying I won't engage in a conversation. I'm comfortable with that.
This is an extremely toxic attitude. I wouldn't be interested in working with you in any professional capacity, on an open-source project, or having you as a friend, and as a matter of fact if someone expressed this attitude at work I would complain to HR.
The more freedom we take away, the higher quality software we produce. Looking at you, Rust.
How are you measuring this?
All kidding aside, obviously I don't. That shouldn't detract from my statement at all though, nobody measures anything about software. We're all in the dark. Thats actually reinforcing my beliefs: we are in the dark because software is unmeasurable because of the freedoms taken by programmers that fail to abstract or fail to be formalized.
Formalizing software, and then starting to measure changes rigourously, is the first step we have to take towards becoming a mature industry.
Formalization is the oposite of freedom. People think software is like art, where freedom of expression broadens horizons. This is no longer true, most software is not art, most software is buttons that make money when clicked.
Artists live by constraints. Nobody prefer a blank canvas.
Abstract art enjoyes would like to have a word with you.
What's next, sit silently behind the piano for four and a half minutes and call it a musical performance??
Abstract art enjoyers would like to have a word with you.
"Aha!" you say, "but by enforcing conditional checks at variable definition and inside function signatures we don't have to check them ourselves in our code and the compiler itself can check them to ensure correctness and optimize performance!"
...and I'd agree with that sentiment but I'd also add that by having to reason about your types constantly you're adding a non-trivial amount of mental overhead when structuring your code. For some people that's how they reason about their code anyway but for others, having to consider whether or not a number is a `u8`, `u32`, or `usize` adds a lot of complexity to the code that could very well be entirely unnecessary depending on the use case.
Consider, for example a program that's meant to be executed on the command line that takes two numbers and adds them together. No matter what language you use you have to convert the raw string input into a numerical type before performing the addition.
In Python you have a simple function, `int()` that will happily turn any string consisting of numbers into a proper integer that can be used for math operations. If it gets something that isn't a number it'll throw an exception which is trivially easy to detect and wrap in a `try`/`except` block with a user-friendly error message. If we wanted to support floating point numbers we could just use `float()` and the code would otherwise be exactly the same.
In strongly-and-statically-typed languages this same process involves considerably more overhead. Before we can select a function to convert a string into a number we must first pick a numerical type and before we can do that we must think real hard about how big of a number we're going to support in this application. Our function signature(s) could also get complicated because, "wait: what type of string are we going to get from the command line?" Something as simple as adding two numbers becomes complicated really quickly!
Comparing the two ways of handling things (strongly, statically typed VS strongly, dynamically typed), Python ends up the clear winner a lot of the time because you got the job done without having to think as much, with less code that's super easy to reason about even though it didn't use static typing.
The other problem is that when you write such trivial programs in strongly, statically-typed languages like C, C++, Rust, etc it requires a lot more mental overhead to look at the code to figure out what's going on/how it works (though with Rust, less so because there's not 1001 awful ways to do things haha).
Except this has literally nothing to do with static typing and everything to do with whether or not the language you're using cares about numeric types.
For instance in typescript you'd just have `number` as the type. You could conceive of a dynamically typed language like python where adding a u8 to a u32 would cause a runtime error. In which case it sure would be nice to have the compiler tell you to convert before you called a function.
>The other problem is that when you write such trivial programs
If only I had a career writing trivial programs that would make a lot of things easier.
For starters this is not the case, it does not work on a string of numbers:
> Before we can select a function to convert a string into a number we must first pick a numerical type and before we can do that we must think real hard about how big of a number we're going to support in this applicationAren't you doing the exact same thing in Python by picking `int()` over `float()`?
> what type of string are we going to get from the command line?
Well, this might be the case for system languages, but most have a single `String` type.
Here is the program you described in Python
And here it's in Haskell Accepting a single line of numbers is not much different I didn't think much about it, it's not a lot of code and I guess it's easy to reason about?We have projects in java7 still which span thousands of LoC and juniors can just jump in using their IDE and make some sort of contribution.
With python this takes much longer.
I’ve been through several typescript projects where ‘as unknown as any’ makes me wish we didn’t even have typescript in the first place.
If you can’t trust the type system it’s worse than not having one :/
"the language" is not enough per se, you have to use the tools it gives you to gain some advantage: those projects were clearly actively avoiding strong typing, for whatever reason
It's unfortunate that Typescript even provides this casting hack, though it's necessary for gradual, optional typing. The very first thing I would do on such a project is spend the time to fix those broken types, because it will pay back in full in productivity after a short time.
The term "strong typing" doesn't have a clear definition to begin with.
Other terms which it often substitutes do, e.g. static typing, sound type system, decidable type system etc.
e.g. https://perl.plover.com/yak/12views/samples/slide045.html
This seems to be a main reason certain people like it. It gives a sense of productivity. However I think it is misguided. It’s a false sense of productivity. Can they commit? Maybe, yes. Will it be right? Maybe, and more likely with types to handhold. But does it mean they understand the data model and the domain of the codebase they’re working in? No. No typing forces you to know what your code is actually doing.
It takes longer to be productive, but you’ll be productive because you know what you’re doing. Not because the IDE held your hand.
Widget factories or scrum farms will no doubt like the “ease” of jumping into a codebase that has typing, but I’m not yet convinced it’s better or that much better for the experience developer. I need to think on it more. For certain though I’ve seen enough in my time to know that how quickly a junior can “just jump in and make some sort of contribution” is not a good measurement.
You're 100% right though.
Of course abstractions can be misunderstood and misused, but is that an argument for not having them?
In fact, if all the developers change, it's a virtual certainty that none will ever understand the python code, while the odds are about even for java.
(But then, the types aren't the only factor for that.)
You are making a false dichotomy.
It's perfectly possible to work in a dynamic codebase without understanding the domain, business, logic or big picture. And it's just as perfectly possible to build a statically typed codebase that forces you to understand the whole entirety before being able to make a change.
Now, whether it's actuallygood to enforce true understanding of the Whole, before being able to work on a subset, is another debate. One that, unsurprisingly, has long been proven to be false. It's why we consider modules, functions, boundaries, coupling, classes, microservices, layers, and so fort and so on.
I'd tend to think the important feature here is strong typing, not static typing, and indeed TypeScript, which is hampered by the anemic type system of its parent JavaScript, it not the best example of strong typing.
Ultimately, strong typing is a hill I'm willing to die on, too. But a lot of people making arguments on this topic are conflating it with static typing, which just isn't the same thing. In general, that gives me the impression you haven't done enough work in enough different languages to really have an informed opinion. The type systems of different languages work within the context of the language as a whole, with tools like REPLs, test frameworks, constraint checkers, static analyzers, and IDEs carrying a lot of the work that might be attributed to a type system. There are a lot of confounding factors here, and if you're only talking about a few languages and you are making mistakes like conflating static=strong and dynamic=weak, you don't have the breadth of experience to understand what the benefits and downsides of strong types are.
Though uncommon, strong dynamic type systems do exist. Scheme is probably the most popular example, although its type system isn't the strongest. This is the approach I'm taking with Fur[1], which attempts to have as strong a type system as possible while still being dynamic. Python is... stronger than many other popular languages (i.e. JavaScript), but still does a lot of coercion (particularly around truth-y/false-y values--duck typing is a bit of a grey area in that it's sort of arguably weak typing, but also arguably indicates a capable type system rather than a weak type system).
But perhaps more critically, weak static type systems also exist, such as C, which is the source of many of the world's most serious bugs. It's highly unwise to assume that static typing is strong typing.
[1] https://github.com/kerkeslager/fur
Since the author is willing to die for it, they might want to know that they probably meant “dynamically typed”. Some dynamically typed languages are strong typed, too!
I don’t really see any arguments against dynamic typing in there.
Most strong, dynamically typed languages also have good developer tooling (elixir and dialyzer for example) and have built in ways of describing data structures.
Strong typing really shines when you have generics, saving bugs at one level above basic types..
I like the idea, feels very pythonic ironically. And I suppose if you don't want the generic, I imagine there is probably some subclass of that which is more restrictive.
Anyway, as a developer I love when people do this. As a developer this sounds like a nightmare to maintain. My coworkers have told me to limit the numbers of 'what ifs' and just roll with it.
Granted it's not always easy and straight forward but that what senior developers in the team are for..
https://docs.swift.org/swift-book/documentation/the-swift-pr...
Look at duck typing.
Static typing, maybe not so much.
@irrational Sorry for the plagiarism.
Over the years, I have done programming with C, C++, Java, C#, PHP, Python, OCaml, Lua, Nim, Golang, Rust, Objective-C, Flash, Bash, D, Scala, TypeScript, assembly, PL/SQL, F#, and a few that I am forgetting.
Compile time type checking can definitely make things easier.
But for dynamic languages that I have been programming in for many years to have types shoehorned into them as an afterthought feels like a kludge.
And it's a tradeoff. I got used to developing in Node or Python with vim without auto completion (although I had those things as a kid in IDEs for other languages and they can be great). And without compile time type checks.
You can pass JSON around and literally never do a database migration.
This stuff will lead to more runtime errors initially, but also means more streamlined code. And you have to do thorough testing regardless.
TypeScript might be a choice for a large project with several developers. But I would also argue that an even better choice would be just to use a different language that has the typing system you want built in rather than awkwardly bolted on.
What I see in most TypeScript projects is a half-assed attempt that still results in run time type errors, but with the added "benefit" of an awkward bolted on typing system and the need for more tools as well as losing any dynamic benefit.
They drop the idea of using JSON and make everything related to the database compile time checked if possible. I'm not saying that those checks can't be useful but if are going that route it would make more sense to just drop the dynamic language.
Having all of the dynamism can be a convenience and save you time and effort in other ways. But not if you treat it as a poor man's static language and shoehorn in types in a half assed way using a traditional database.
Part of this is that people think software engineering is about your choice of stack or adding processes to development.
What matters most is probably the feedback loop between the users and the developers. Starting with the requirements engineering. Second to that would be details of the software design and organization. Leveraging existing code effectively can be key. Using descriptive but relatively concise identifiers, short functions, good code organization.
But diving into and evaluating all of that stuff in detail takes effort and a lot of skill and so many make snap judgements based on surface level processes or tool selection.
Probably the biggest issue with python.
Seemingly random changes between float64 and object happen when doing unit testing(pandas).
Can't remember the exact situation because I switched to While permanently, but there was some For loop problem. If the data came in as length 1, the for loop would read the individual chars of the string. If it came in as >1 it would iterate through the list.
15 years ago when I started python, maybe it made sense. Probably ~5-6 'oof that was a bug I wasted time' in 3 years professional use. Today, I find myself forcing types to solve a bug, or at a minimum checking the types as the program progresses.
What you call flexibility I call non-readability:
If you stop working with it for 2 months you forget what each one is doing and when you are supposed to use it.Also, note that pandas's datafrimes shine when you focus on column operations, if you're finding yourself accessing individual elements by index often, that's usually a sign that you should switch to a more appropriate data structure.
That when you look at online examples or other people's code you will find all of these variants used.
Sure, doing an operation in Polars can be more verbose, but it really does have just a few ways of doing things. Which in the end is actually more productive, since you don't need to always google the syntax.
You probably did this:
The correct form for the first one is this:Common Lisp seems to be the best of both worlds, with dynamic strong typing in general and advanced compiler type inference that can correctly point out mismatched types in most cases in SBCL, leading to much easier time programming interactively.
Try ML (OCaml, F#…) or Haskell, they have type inference. No need to declare types most of the time, but they’re there, checked at compile time.
c# pretty much has that nailed. You can do dynamic typing if you want, but you're on your own if it fucks up. Moreover its obvious when you're doing dynamic silliness.
Python is strongly typed, the problem is that its also duck typed and everything is dynamic. Sure there is type hinting, but that's mostly optional and pretty much broken as its not really supported at run time. (unless I've missed something)
What I'd really like is something like perl's "use strict;" in python.
however I don't see that coming anytime soon.
You can still get runtime bugs, of course, but that kind of thing would manifest as a TypeError or something. All of those bugs could be caught with tests.
I like dynamically typed languages. They are simply the pragmatic choice for the vast majority of code that will ever be written. But being dynamic doesn't mean you can't do types. Python has optional type hinting and checkers like mypy are very good. Common Lisp has features that enable you to declare types and get near-native speed binaries.
These days I do type my Python code for many of the reasons given in the article, but I still enjoy using a dynamically typed language underneath.
That's more about type coercion which you can avoid in dynamic languages. See === vs == in JavaScript.
https://clojure.org/about/spec#_expressivity_proof
let person1 = new_person();
i prefer the c++ way (which also supports type inference):
Person person1;
Types are good, but as with anything else people take it too far. Making it your life goal to encode all business logic in the type system results in an even more incomprehensible mess than not having types at all. If the type name can't fit on one line in the error message, you've gone too far.
I'm forever grateful for Typescript, but I wouldn't be surprised to see some of that code in a future research paper titled "The era when types went too far".
I think TS is a special case, since you can "just say no", but in languages with a statically typed runtime/no runtime I try to minimize the amount of <> in a type, even if they're hidden behind a type alias or inference.
That's just because the code is bad, not because the type is bad.
That's literally just the definition of, this code is so bad, let's just not type it.
A lot of web devs these days don't remember a time before CSS but, back then, we used to use tables for page layout. This was really an abuse of the `table` element of HTML, which was designed to display tabular data, but it was basically the only way to achieve many layouts like having a side menu bar, for example.
Abusing `table` was bad for many reasons, including accessibility. People started to care about the semantic web and remembered that HTML is a markup language for content, not a style language for layout. So people advocated for using CSS for layout and reserving `table` for actual table data.
But gradually this rule became both simpler and more ridiculously followed. People started talking about "using divs instead of table". They would look at a page source and if they saw `div` it would get a nod, but if they saw `table` the developer would be relentlessly abused because `table` is old-fashioned. Eventually, of course, someone reinvented tables using divs and CSS.
And then people realised the pendulum had swung too far.
I really wonder how people can actually work like that. Is there some overarching methodology like super strict tests with 100% code coverage or so?
I don't see how either of these things relate to type discussions. A typo can certainly lead to incorrect code in the most strongly typed, staticest language there is (otherwise, what the hell is writing code even for?) What's worse: a runtime error or no error that produces the wrong result? Running a Python project might be quicker than compiling a C++ project. Dynamically typed languages can do better than grepping for function calls.
Yes but the >99% of possible typos (and nearly 100% of typos in identifier names) will be caught by the compiler. That's good enough for me to say "static typing helps with typos".
> What's worse: a runtime error or no error that produces the wrong result
From most bad to least bad:
> Running a Python project might be quicker than compiling a C++ projectYes, C++ is an insane language. You can have large C++ projects that compile fast, but you have to spend a non-zero amount of engineering effort. Generally "avoid templates" and "write the code in .cpp files, not headers" will ensure that you won't have hour-long compiles.
This is a problem with C++, not with type-checking in general.
> Dynamically typed languages can do better than grepping for function calls.
I'd love to hear how, I was doing quite a bit of JS at my previous job (not TypeScript, couldn't convince them :) and I never found a more reliable way to find callsites/variable usages, than grepping for the function name. And even that is not 100% guarentee, since you can call a function in some pretty insane ways,
and then good luck finding this callsite when searching for anObject.asdf123You still have to grep for callsites, although the scope is smaller. A much better way to reduce the scope is with modules - if a function is module-internal, you only have to grep that module for callsites. Better than having to scan the entire codebase, worse than not having to do it at all.
Performance with microservices + dynamic language is unjustifiably terrible if you're the one who's paying for the servers. You're already eating a 100x slowdown for using a scripting language, add microservices and you're eating easily another 100x for replacing regular function calls with network packets. I'm not willing to run a k8s cluster for a program my laptop could handle had I used the hardware better.
That's all assuming microservices are even an option, which is a very tiny percentage of all software.
You know, until the program is non-trivial: passing data between systems on a queue; using async, threading, and/or multiprocessing; compiled binary critical-performance libraries… and I’m left wishing I’d written the whole thing in Erlang.
Every time I see a developer make this complaint about static typing I wonder what the hell they're doing with their type definitions and architecture where this is actually a problem.
> and pray you fixed them all.
If this isn't detected for you at compile/build time you're not using static typing
The real discussion is not about types vs no-types, it's about the role and place for each of these bending of the rule and their associated tradeoff in different contexts. Op even takes inference for granted towards the end of the post, but make no mistake, that's a form of weak(er) typing.
For example, writing template code in C++ without frequent use of type inference (aka. auto) can be an absolute nightmare (I suspect that extends to generics programming at large, but C++ is where I have the most experience here), and the legibility gains from making use of it for iterators is broadly acknowledged as being easily worth the obfuscation for scope-limited variables. But there's also a strong case being made to avoid its use entirely in most other contexts.
Java by contrast has a greatly simplified generic system; Scala improves on that with robust type inference, making generics pretty trivial to write and use.
Wait, I've never looked at it. I guess that's why I used the word trust. Surely it confirms my preferences.
As a falloff expect decades of engineers lamenting overly engineered, overly verbose, overly complex TS legacy apps.
If anything, the dynamic typing bubble is bursting right now.
But then again, I suppose we haven't met.
It is because the enterprise TS supporters are too loud.
There are many here saying they consider people that dislike TS to be idiots and they don't lower themselves to discuss with those who think JS is preferable.
They don't wanna argue, we don't wanna argue. Life is great.
I don't think we're near the peak yet. We're still on the way up. I've seen $5M projects turn into $50M projects in the space of 5 years and nobody else in my little consulting bubble has noticed at all lol. It shouldn't take 100 devs to build a SaaS with 5 forms and a dashboard but here we are...
https://youtu.be/dCuZkaaou0Q?feature=shared&t=539
Now go and rewrite your dynamic language's runtime in a dynamic language.
https://github.com/robert-strandh/SICL (which I wrote a decent chunk of the compiler backend of.)