> Could you explain what descriptive and expressive power is missing here?
tldr: i haven't seen a runtime typechecker that handles generics and function types in a satisfactory manner.
i've used various Python libraries for runtime type-checking (based on `typing` annotations) like `typeguard`. and they work okay for simple types, but suck for anything involving generics and function types. checking if something is a `List[int]` every time it's passed as a parameter is too expensive, because you have to go through the whole list (and you're out of luck if it's an Iterator[int] - can't traverse that without exhausting it). runtime typecheckers don't have enough information to check if something is a valid `CustomList[int]`. and they have no way of checking if e.g. a function (passed as a parameter) is actually a `str -> int`, at best you'll find out when you call it.
and runtime checkers, at least the ones i've used, often end up requiring more annotations than i'd have to write in a type-inferred language. it might be possible to work around that to some degree, but I think that's a fundamental limitation – unlike a static checker, they only have info about code that already ran. so you'll have to annotate code like this:
f xs = cons 'a' xs
because a runtime checker can't "look into the future" and tell that based on the usage of `cons`, the only sensible type for `xs` is List[Char], so `f` must be of type `List[Char] -> List[Char]`.
> i've used various Python libraries for runtime type-checking (based on `typing` annotations) like `typeguard`.
Let's just stop right there, since it's immediately clear you aren't answering the question I asked. You're talking about type checking, not descriptive and expressive power.
The topic is whether dynamic types are suitable for domain modeling, not whether dynamic types provide static type checking. We all agree that dynamic types don't provide static type checking.
alright, just to clarify, from the comment that introduced expressive power into the discussion:
> Specs/contracts are cool but ultimately don't afford the same kind of descriptive and expressive power that a static type system does.
i understand that as "the ability to describe/enforce the domain's rules".
now, i guess i made a bit of leap, jumping to runtime typechecking. my thinking was that while modelling your domain, you might want to specify that e.g. `width` and `height` must at least be numeric (i know i would!); `typeguard` et al are a concise way of doing that in Python, but have their limitations. so static types can be more "expressive" if the tools i mentioned (generics and function types) are useful for describing your domain model, which i often find to be the case.
looks like i missed the mark there; but in that case, i'm not sure what point you're making with that Square class. it's hard to say what's missing (or not) because there's not a lot there.
Well, let's be clear here: the code already expresses that height and width are numeric. You read the code and knew they were numeric; QED. In fact, if you think a bit deeper, you probably know a few more things about the type of those variables: they're positive, for example.
And if you're using a mainstream statically typed programming language, you probably aren't actually going to express that it's a numeric using the type system. 90% of the time someone will just throw `int` on there and call it a day, and that type expresses a bunch of lies about the height and width: it says it can be negative, and it says it can't be a decimal, and it says it can't be greater than the INT_MAX of you machine. `unsigned long` and `double` are both a little better, but are still expressing a bunch of lies. And that's if you are in a more modern language: in C, for example, you're telling the compiler that it's totally cool to add the side_length to a char* and (depending on you settings) not even warn about it.
So I have to ask, where exactly is this expressive power you're talking about? You're only gaining the ability to express things to the compiler, not humans, and it doesn't actually give you the ability to express what you want to express to the compiler. You've eliminated the narrow class of bugs where:
1. The code passes in a non-numeric.
2. The bug occurs on a path not traversed in normal testing.
And you've done this at the cost of your code not being able to handle a lot of the numeric values you might want to be able to handle.
honestly, if this is going to be about static vs dynamic typing in general, i'm going to peace out because this argument has been rehashed about a million times. and we seem to be talking past each other anyway
i just wanted to make a point about how checking types at runtime doesn't mesh well with generics and functions, so it's always going to be limited on that axis. and perhaps there's a dynamically typed nirvana where you just handle that differently, but it's a problem i personally had
tldr: i haven't seen a runtime typechecker that handles generics and function types in a satisfactory manner.
i've used various Python libraries for runtime type-checking (based on `typing` annotations) like `typeguard`. and they work okay for simple types, but suck for anything involving generics and function types. checking if something is a `List[int]` every time it's passed as a parameter is too expensive, because you have to go through the whole list (and you're out of luck if it's an Iterator[int] - can't traverse that without exhausting it). runtime typecheckers don't have enough information to check if something is a valid `CustomList[int]`. and they have no way of checking if e.g. a function (passed as a parameter) is actually a `str -> int`, at best you'll find out when you call it.
and runtime checkers, at least the ones i've used, often end up requiring more annotations than i'd have to write in a type-inferred language. it might be possible to work around that to some degree, but I think that's a fundamental limitation – unlike a static checker, they only have info about code that already ran. so you'll have to annotate code like this:
because a runtime checker can't "look into the future" and tell that based on the usage of `cons`, the only sensible type for `xs` is List[Char], so `f` must be of type `List[Char] -> List[Char]`.