Comment by _flux

1 day ago

There's a school of thought that consider the term "types" reflect to the properties that exist in programs even before they are run, as in they are a property of the programs themselves, not their state at runtime. This thinking—which is also what type theory talks about—does consider Python untyped: reading a Python program along with its specification, you are not able to assign types to each expression.

But what Python does have is tagging: when you create an object you tag it, and then whenever you operate on those values, you check the tag and maybe raise an exception or not. This is happening at runtime.

Strongly typed and weakly typed do not seem to have good definitions. A good one I've read is that "strong typing describes the typing you like".

It is great though if people go to the same extent as you to define what they are talking about, as this reduces the chances of misunderstandings. But it should not be taken as fact that the definitions you have chosen are the universally accepted ones.

> Strongly typed and weakly typed do not seem to have good definitions.

Is strongly typed not “I compiler/runtime guarantee the bytes I read adhere to type T”?

  • There's a lot of nuance to that statement. Most languages, including e.g. Java or Typescript, would not be strongly typed according to your definition, because their type system is "unsound": there are known cases where the type system does not protect you and the types are wrong. We generally still call these languages strongly typed.

    In Typescript this is by design. The most obvious is array variance. Typescript makes them covariant because that's what a lot of sane TS and JS code uses them as, but they should be invariant because you can write to them.

    Example:

       const dogs: Dog[] = []
       // A sound type system would error here,
       // but there's too many useful cases where you want to do this
       const animals: Animal[] = dogs 
       animals.push(new Cat())
       animals[0].bark() // runtime TypeError here

    • Okay, so I'm not crazy for thinking that declaring an empty, typed array as `const` and then writing/pushing to it is confusing/feels wrong.

      I didn't go to college for software engineering or anything so when I ran into that for the first time I assumed there must have been some good academic reason that was simply beyond me as to why it was done that way.

      It turns out that no, it's just as weird to those that do have the formal background, boy am I feeling vindicated ;)

      2 replies →

    • I may be missing something, but your example doesn't typecheck?

        class Animal { }
        class Dog extends Animal{
          bark(){return 1}
        }
        class Cat extends Animal{
          bark(){return 1}
        }
        const dogs: Dog[] = []
        const animals: Animal[] = dogs
        animals.push(new Cat())
        animals[0].bark() <<<<< "Property 'bark' does not exist on type 'Animal'."

      1 reply →

    • I would have called this “strictly typed” I think, not “strongly”. Maybe my terminology is off.

Note that Python supports type annotations in its syntax, but the interpreter will just ignore them as they are meant to be used by external tools such as ruff: https://github.com/astral-sh/ruff

I've had a good experience developing small to medium sized Python programs/scripts using type annotations plus ruff connected to my editor through its LSP (Language Server Protocol). It helps a lot, and I don't like to write Python without it.

You can create types from literal values and use union types, which I feel makes it more pleasant than Go's type system for example.

  • Definitely mypy keeps my sanity in a relatively large Python project I'm working on :). But I don't believe Python actually assigns any meaning to them, and at times mypy and e.g. pyright may disagree on what the types actually are.

That's fair, and I don't claim that I have the canonically correct answers. My broader claim is that I don't think I've ever heard someone say ugh, I despise that my bucket of bytes has an associated type! The real discussions weren't against types, but against various type disciplines.

For example, I find it highly annoying to have to sprinkle type annotations all over the place when the compiler isn't smart enough to figure out what I mean, in the absence of ambiguity. Like imagine this C code:

  int main() {
      int i = 23;
      auto j = i;
      printf("i = %d, j = %d\n", i, j);
  }

There wasn't a great way until recently (C23, I think?) to say "just make j whatever type it needs to be here and don't pester me with it". Contrast with Rust which is strongly, statically typed but also infers types where it can:

  fn foo1() -> i8 {
      23
  }
  
  fn foo2() -> String {
      "foo2".into()
  }
  
  fn main() {
      let f1 = foo1();
      let f2 = foo2();
      let f3 = f1 + f2;
      println!("Hello, world!");
  }

Here, that bit in "foo2" says "cast this str into whatever type you can infer it's suppose to be". Since it's going to be the return value of a function that returns a String, it must be a String, so Rust casts it to a String. Similarly, the first line of main() says f1 is an i8 because it's assigned to something that returns an i8. f2's a String for the same reason. The f3 line is an error because you can't add an i8 and a String, and Rust can figure all that out without having to annotate f1 or f2.

I love Rust's typing because it's helpful and makes strong guarantees about the program's correctness. I'm not "anti-typing" at all. I'm just not a big fan of languages that make you annotate everything everywhere. Back when such arguments were in fashion, a pre-auto C fan might reduce my whole argument to "you don't like typing, newbie!", which would make me roll my eyes and hand them a lollipop.

FWIW, I think TypeScript's pretty great. I never like JS. I tolerated it, and could use it, but didn't enjoy it at all. TS is fun, though.

  • This is called automatic type inference, and it is a big feature of functional programming languages, many of which are very strongly typed. Also, for the record, C++ gained type inference about a decade and a half ago.

    In C++ one can declare a completely typeless lambda:

      auto callsAdd = [](auto x, auto y) { return x + y; }
    

    And the programmer need never specify what x and y are, as long as there exists a reachable declaration of operator+ that has two arguments that accepts whatever x and y resolve to, at instantiation time (which is compile time).

  • I think it's pretty widely recognized these days that type inference for local variables is a good idea. Most major languages that didn't previously have it have since added it.