11

In my understanding, the strengths of not using type are flexible and newbie-friendly, and the strengths of using type are easier to debug and reading code. However now you can use types on dynamically typed languages like Python or Javascript (technically on Typescript) which seems to me having the best of both worlds. So is there any type-relevant reason to use statically typed languages? What can they do that even the addition of type in dynamically typed languages can't do?

18

9 Answers 9

9

What can they do that even the addition of type in dynamically typed languages can't do?

TL;DR - Static Analysis (including the standard checks the compiler does).

As the toolchain / editors get better this becomes more of question of degree rather than a yes/no factor.

In that we are seeing more static analysis performed by editors and tooling for dynamically typed languages - however if type hinting is optional and/or some automatic casting is performed at runtime the static analysis has to allow for that and may not find as many problems as it is possible to find in a static and strongly typed language. Further other features of the language can be checked statically for example it is possible to check for private methods - again this isn't exclusive to statically typed languages however dynamic languages have more flexibility in this area and if a language provides a mechanism to override the method protections the static analysis can't generate a hard error for this violation.

In short static typing is a set of hard rules, if you make the rules "harder" for dynamically typed languages you can get closer to the benefits of statically typed languages, but at some point you create a statically typed language.

Another thing to note is that on the "edge" of our software (even that written in strongly+statically typed languages) we tend to switch to more loose typing:

  • A lot of code is written without static analysis of SQL statements / SQLs that are built dynamically.
  • When generating web pages on the server side we often use templating languages.
  • Sometimes we (de)serialize in generic ways for example to JSON.
3
  • On the other hand, we also sometimes use DSLs to provide even stronger typing than common objects and primitives. Dec 8, 2023 at 11:21
  • 2
    JSON is a good example. I've seen some cases where Typescript actually made finding a bug slower for new developers: A mismatch/typo between the backend api model and the frontend typescript model. At runtime the types are gone and Javascript happily sets the property with a typo and doesn't raise an error. The developer sees the Typescript Model and assumes it represents the truth, while it actually looks different in Javascript runtime Dec 8, 2023 at 14:47
  • 1
    @JineappleAra why is anyone transcribing by hand the backend api model? Do that with a tool
    – Caleth
    Dec 8, 2023 at 16:21
37

flexible and newbie-friendly, and the strengths of using type are easier to debug and reading code.

There's a contradiction here: surely newbies might also want code to be easier to debug, and to read?

The reality is not that simple. To me, the important distinction is between:

  • type errors at compile time, or development time before running the program

  • type errors at runtime, after running the program, including in production deployment

What newbies find advantageous about the Python and Javascript approaches is that you can quickly write a program that works on the "happy path" without having to think too hard about all the possible cases. Such programs are more likely to fail on malformed input or in unforseen cirmstances.

Whereas aggressive type checking moves the thinking up front. It forces you to constrain parts of the system and specify what is valid and what is not. This makes it harder to write a program that will run, but more likely that you will get correct results when it does. (Users of Rust and ML are particularly likely to report the "as soon as I finished getting it to compile, it worked first time when run" experience).

Edit: something that is easier to do with a static system is type erasure, which in turn affects the design of the runtime and how code is compiled (either just-in-time or ahead-of-time). Whereas in a dynamic system all the type information has to be carried around all the time because you cannot prove that you don't need it. This is made explicit to developers who use C#/Roslyn, for example: if you turn on AOT compilation, a lot of features relating to reflection are disabled. Because the type information does not exist in the compiled code.

A well-designed type system can enable a compiler to make the runtime significantly faster.

7
  • 19
    I think one problem is that a lot of beginners get introduced to static typing through type system such as Java's. But Java has all of the disadvantages of static typing with only a tiny subset of the advantages. ML or Haskell allow you to express a lot more interesting properties than Java does. When you say "I promise to return a string" in Java, it is perfectly legal to return null. Or not return at all. Or throw an exception. When you say "I promise to return a sorted list" in, say, Idris, it is only legal to return a sorted list. Period. Dec 7, 2023 at 13:09
  • 1
    The optional type checking happens at compile time, yes. What happens at runtime depends on the language, but that's done with the "concrete" types of the objects as it actually happens. e.g. if you call "len()" on something that is not countable in Python.
    – pjc50
    Dec 7, 2023 at 14:24
  • 1
    @Ooker it can, because you are describing a statically typed language. Or sometimes can, because you are describing a gradually typed language
    – Caleth
    Dec 7, 2023 at 15:20
  • 1
    @Ooker the point of dynamic typing is that you cannot always know a value at compile time. This is the definition of dynamic. Often, this right is surrendered by the programmer or there may be logic that brackets possible runtime values. However, in a static language, types can always be checked at compile time. Dec 7, 2023 at 21:17
  • 1
    @JörgWMittag this discussion is far older than Java, so you can’t blame it. And what are “all of the disadvantages of static typing” anyway? If you compare them with a “tiny subset of the advantages” you have to be more explicit to allow to judge the amount objectively.
    – Holger
    Dec 8, 2023 at 10:25
21

I'll tell you the exact point where the benefit becomes obvious: the point where you need to make a change to something that's used multiple times throughout your project, that will necessarily impact the other code that uses it in some way.

In a statically typed language, you make the change, run a build, and the compiler will tell you everything that you just broke with that change, so you immediately know exactly what to fix. In a dynamically-typed language, all those same points still need to be updated, but you have a much, much harder time finding out about them.

5
  • 2
    Can't dynamically typed languages with type annotation tell me everything that I just broke with that change?
    – Ooker
    Dec 8, 2023 at 1:51
  • 15
    @Ooker only the places where you have type annotations. If you have enforcement of types everywhere, you aren't a dynamically typed language anymore
    – Caleth
    Dec 8, 2023 at 9:01
  • 2
    This exact thing allowed me to make a massive change in my app just days before go-live based on a surprise requirement from a stupid boss. Because I had done my type defs smartly, I was able to redesign a crucial data structure and locate all impacts within minutes. // ETA: if you do your types dumbly, you gain nothing and typing hurts you. Like any tool, you have to use it right.
    – Tom
    Dec 9, 2023 at 4:09
  • I still don't like this idea that types make your changes safe. There are plenty of circumstances where the type is the same but the data changes and still causes bugs.
    – aaaaaa
    Dec 9, 2023 at 15:55
  • @aaaaaa not "safe" so much as safer. The only thing that's truly safe is not making any changes at all (and even that could be considered "unsafe" if your codebase already has serious bugs in it, and you aren't willing to try to fix them :) ) Dec 9, 2023 at 22:04
9

The point of higher-level languages is not that they can do more. Assembly language can already do everything that a computer can do.

Their reason for existing is only ever to assist humans, with human-like memory, perception, speed of thought etc., in creating and maintaining code bases with fewer errors. The more aware you are of your own limitations, the more you will appreciate why people invented types, classes, interfaces etc. etc.

15
  • 4
    @Ooker "dynamically typed languages with type annotation" either you can ignore the type annotations, so they only sometimes give you the benefits of types, or they have become a statically typed language
    – Caleth
    Dec 7, 2023 at 15:17
  • 3
    @Ooker: The type annotations are completely meaningless. This is a perfectly legal Python program: a: int = "Hello". The type annotations can't tell you whether there is an error or not because they can't tell you anything at all. They have no defined semantics. Dec 8, 2023 at 7:22
  • 3
    @Ooker: Because the TypeScript language specification assigns a specific meaning to type annotations whereas the Python language specification does not. The TypeScript language specification says "if you annotate a variable with the type number, then the content of that variable must be a number". The Python language specification says "you can annotate a variable with a type, but I will not define what that annotation means". Dec 8, 2023 at 7:47
  • 3
    I downvoted because this really didn't answer the spirit of the question at all, but instead relied on a technicality to give an answer that the question wasn't asking. The question is about the benefits of statically typed language vs optional types. Saying that "both can do the same things" is not an answer to that question. Dec 8, 2023 at 8:32
  • 5
    @JonBentley And I would give that answer again, because I have found that the misunderstanding that more language constructs make more things possible is so fundamental that people who suffer from it are severely impaired in their ability to program productively. SE becomes so much easier once you understand that its reason for being is human, not technical factors. Dec 8, 2023 at 8:46
7

Restrictions are a Good Thing.

Here are examples from C where missing restrictions were later recognized as bug-prone. Where possible, these restrictions were imposed in later versions:

  • Functions implicitly returning int
  • Implicit narrowing
  • Pointer <-> int conversion
  • No type checking in printf
  • No explicit const
  • No explicit function argument declarations
  • No value range checks
  • No array boundary checks

If a language permits you, optionally, to not declare the type of a variable, people will use that option, thus withholding from the compiler information conveying the intent of the programmer. The compiler — who, against first impression, is your friend! — will not be able to diagnose otherwise obvious errors anymore. The result are buggier programs resulting in exploding power plants, crashing space probes and more bitcoin payments.

That's the downside of offering this alternative.

1
  • 1
    And of course, C99's restrict!
    – qwr
    Dec 8, 2023 at 16:45
2

The question is very hard to answer as it is posted - it assumes a lot, and those assumptions are themselves veiled questions.

A language can be static or dynamic; it can be typed or untyped. To confuse things more, both categories can be implemented on a spectrum. For example, you can have a statically typed language which has some form of introspection that allows you to dynamically inject new (themselves statically typed) classes. You can have an untyped language which allows optional type declarations. You can have languages without explicit types but still with some kind of runtime type check in place (i.e., duck typing). At this point in 2023, basically everything has been tried in some form or fashion.

The question of which benefits each approach has cannot be answered in an abstract way without comparing concrete languages. Most languages have a certain area in which they excel, and others where they are maybe not the first choice. A kernel developer writing a low level module might be better off using C; a web developer working on small internally used web apps with quick turnover might be better off with a language that has a robust ecosystem for that. Someone implementing sandboxed apps (think of the modules in a car entertainment system) might have wildly different needs from someone programming tooling for a container orchestrator. This may lead to different typing systems being really useful in some areas, and not so much in others.

Finally, for many areas of software development, the biggest cost is not in writing the software itself, but by keeping the knowledge about the software around in the company, and being able to maintain and extend it over decades. This may, again, lead to different decisions around languages and typing systems. While a statically language with relatively restrictive typing might not be "sexy" to programmers, and maybe take longer to write the initial code, it for sure can be very useful in the very long term, if it makes it relatively easy to decide if a given change has big sweeping impact; or to find where that impact might be occurring, by being able to use static type analysis in the IDE, long before compiling or running anything.

So TLDR: it makes no sense to ask where the break-even point between statically and dynamically typed languages are. Look at the actual languages instead, and compare them to the intended use. This will give you a multidimensional decision tree, which you have to combine with your own or your programmer's skills, with your long-term plans, and so on and forth.

2
  • Personally I find the compiler able to do magic with types to be "sexy". To each his own!
    – qwr
    Dec 8, 2023 at 16:47
  • Absolutely, yes. In a static language, the confidence from the compilation not throwing any errors is very nice.
    – AnoE
    Dec 11, 2023 at 9:48
2

When you add any feature to an existing language, you are really making a new language, albeit one which can share the existing ecosystem of the previous language.

Adding type annotations to a dynamically typed language, there are two choices you can make:

  1. The types are optional.

The new language is gradually typed, i.e. a hybrid of static and dynamic. You get the benefits of static typing where you use it, but you lose the benefits of dynamic typing there. As other answers point out, some of the benefits of static typing require the checks to be global, so you don't have the full benefit of static typing.

  1. The types are mandatory

The new language is a statically typed language. You've lost the benefits of dynamic typing, but gained the benefits of static typing.

Thus your question is really asking: "Why would you use a statically typed language, when you could use a statically typed language?", which is nonsensical as a question.

4
  • I see. It seems that the origins of my confusions are thinking that Python's type annotation makes it the same with TypeScript, and TypeScript is only a gradually typed language, not a true statically typed language. However you can choose to opt-in many strict flags in TS, thus making the mandatory typing an option as well. In other words, you can choose TS to be a gradually typed language or statically typed language in the config. What do you think about this?
    – Ooker
    Dec 9, 2023 at 17:20
  • @Ooker strict vs non strict is giving choice between these two options. Each config is a slightly different language
    – Caleth
    Dec 11, 2023 at 18:48
  • Do typical statically typed languages have this non-stick option? I suppose no
    – Ooker
    Dec 12, 2023 at 1:01
  • 1
    @Ooker you have things like gcc's -Werror flag that mark more things as static violations
    – Caleth
    Dec 12, 2023 at 9:14
1

In a statically-typed just-in-time (JIT)-compiled language like C#, if someone has a function:

int sum(int a, int b) { return a+b; }

the JIT can generate machine code for the function that will use the processor's "ADD" instruction to directly add the operands and return the result, without having to first check whether the arguments represent values of type int. Further, code that calls the function needs to pass the values of the two arguments, but doesn't need to pass any information about the types. These things are possible because:

  1. The JIT won't allow the address of any function that blindly expects to receive two arguments of type int to be made available to anything that would either pass two int values and call it directly, or store it in a region of storage desginated exclusively for the storage of pointers to such functions.

  2. The JIT won't allow code to prepare two int arguments and call a function without saying anything about the types involved unless the function is one that is declared as expecting two int arguments, or the address was retrieved from a region of storage designated exclusively for the storage of pointers to such functions.

Although C# does have an explicit dynamic type (which I've never used), and also allows functions that acccept object to be passed simple types like int by constructing temporary value-holder objects and passing references to them, it's easier and vastly more common for programs to pass integer values as int.

There are situations where it's useful for a statically-typed heap-based language to have a type that can be used to represent a "general-purpose bunch of stuff" in a manner that encapsulates information about its own schema, analogous to a Javascript "Object". C# can do that either with a dynamic object or a Dictionary<String,Object>; both approaches have advantages and disadvantages. These can be very useful when dealing with exchanging data with other programs that may use slightly different schemas. In general, however, if the schema associate with a type is known in advance, static typing will allow more efficient code generation.

0

There is one point where static types become superior: When your application grows and you suddenly have multiple completely unrelated methods with the same name. You see x.method() in your source code. Whoever wrote it knew what it meant at the time, but some time later, you don’t know. So suddenly you have to hunt down what kind of object x is to find what method is called.

1
  • 2
    But then an optionally type annotation can solve that
    – Ooker
    Dec 9, 2023 at 3:02

Not the answer you're looking for? Browse other questions tagged or ask your own question.