Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
We don't need runtime type checks (stitcher.io)
18 points by brentroose on March 3, 2022 | hide | past | favorite | 29 comments


Some thoughts:

> Because, here's the thing about runtime type checks: they are a debugging device, not so much a safety net.

Yes, knowing why something crashed is a debugging tool and a pretty useful one at that.

But what are you gaining by turning off the runtime type checker? It's not clear. PHP in particular tends to be IO not CPU bound and the people who have CPU performance issues with PHP are so few. What problem is being solved here?

But the author seems to be arguging for static type checking in PHP. You won't get an argument against this from me. I consider dynamic typing a false economy and anathema to a large multi-developer code base.

Oh and Hack is a superior PHP too.


> But what are you gaining by turning off the runtime type checker? It's not clear.

Only possibilities are a tiny boost in speed or a tiny improvement in memory footprint? Can't think of anything else.

> PHP in particular tends to be IO not CPU bound and the people who have CPU performance issues with PHP are so few. What problem is being solved here?

Excellent point.

> I consider dynamic typing a false economy and anathema to a large multi-developer code base.

Catch and fix as much as you can statically. Common sense to me. But there are those who passionately argue otherwise.


> When type errors occur in production, the end result is the program crashing, nothing you can do about it.

Uh, you can catch the type error and recover (e.g., as with any other runtime failures, fail the incoming request that triggered the failure, unwinding the stack and reclaiming memory).

And, even if you didn't, crashing may be the ideal reaction: https://github.com/alefore/weblog/blob/master/software-corre...

There are many reasons to prefer static type checks; I just find this specific argument against runtime type checks flawed. Yeah, static type checks are vastly better than runtime type checks; but runtime type checks are vastly better than, uh, no type checks.


This is a false premise or at least a false dichotomy.

Discovering that the car you ordered is a pizza is not something you just... abort and undo/ redo. It's: wtf did you do? My age is "pizza", your name is the function "not", your account balance is a database connection. It's ridiculous to say the least and using a tool that makes that > 0% probability is just awful.

There are so many different typing systems, conflating all of them into "static type checking" in order to have something to pit against "runtime checks" is just not serious.


> Discovering that the car you ordered is a pizza is not something you just... abort and undo/ redo. It's: wtf did you do? My age is "pizza", your name is the function "not", your account balance is a database connection. It's ridiculous to say the least and using a tool that makes that > 0% probability is just awful.

Agree, which is why I think the author proposal of just ignoring them (he's using this as an argument to say that runtime type checks are useless, cause they crash the program) makes no sense.

In practice, if the car I ordered is a pizza, I want to know! In production, thanks to unit/integration tests with reasonable coverage, dynamically typed languages will typically exhibit typing errors in very unusual circumstances, typically in branches or combinations that are rarely exercised (otherwise the testing infrastructure would have uncovered the bug), typically much later after the code was written. So, yeah, I don't think any proposal of "just get rid of the checks" deserves to be taken seriously.

But neither do I think your implicit proposal of "just crash everything cause somewhere some very unusual request received the wrong type" makes sense, at least not for large scale reliable software. Just cancel the very unusual request (and recover safely, and make sure to log the very unexpected situation), but don't turn a single request failure into a huge outage.

I think languages should aim to detect these inconsistencies statically, at compile time, as much as feasible. Failing that, they should aim to detect them at "test time". Failing that, at runtime.


I disagree completely. Having runtime checks or not if the code that's written is basically randomly stringed together expressions, are all unsound code.

This is runtime type-checks: if x == null: die "Nullpointer exception";

it is simply not better than: x.DoTheThing();

They are equivalent.

The solution to that is not a runtime check, it's actually using a typing system in the first place. Runtime type-checks is no different from:

let x = 42; if x != 42: die "It's supposed to be 42"; compute_the_thing x; if x != 42: die "It's supposed to be 42"; save x; if x != 42: die "It's supposed to be 42";

Only completely unsound code would rely on something like that.

I know that I'm not checking types in my pretend-o-code.

"In practice, if the car I ordered is a pizza, I want to know!"

I would say that this is misusing my analogy by flirting with simple value bugs. Ordering a thing and getting the wrong kind of product, that's perfectly understandable in the world of bugs and what not.

Calling the place_order function, expecting to get back an order object but instead getting the function "not" or a database connection, that's more akin to what we're talking about here. It's the wrong data type and having a runtime check to catch that is a sign of dealing with unsound code.

Just crash everything is what _you_ are suggesting because that failing runtime type-check _is_ a de facto runtime crash of whatever was running at that point.

Code that runs the risk of encountering such a problem should not compile because it's not even wrong.


If you order a pizza and get a calzone then you refuse the transaction and raise an error, you don't just fall over in a heap on the floor.


"Was this terrible pizza code written with a folding editor?"


Don't you tell me what to do, you're not my supervisor!


There are many scenarios in dynamic languages where an "incorrect" (i.e. unexpected) type could be provided, but the program would not crash and potentially produce incorrect results or at the very least, hide where the real error occurs.

For example, assume you have Python code to sum up an array (yes, I know there's a builtin, but it's an easy example):

    def calc_sum(arr):
        functools.reduce(lambda x, y: x + y, arr[1:], arr[0])
If we call this with an array of numbers, we get the expected result. However, we can call the function with other types without a crash.

    calc_sum([1, 2, 3]) => 6
    calc_sum(['a', 'b', 'c']) => 'abc'
    calc_sum('abc') => 'abc'
If we get such an input, likely something wrong happened earlier in the stack. If we had a type check, we would find the problem quickly. Without a type check, the problem will pop up somewhere else since we expect the return value to be an integer, but it ends up being a string. Or we might actually have a sequence of events that does not result in a crash at all.

The example is a bit contrived and perhaps doesn't make the strongest argument for runtime type checking, but it at least suggests scenarios where we might detect bugs earlier.


That works wonderful as long as you have a flexible type system like Typescript. For me io-ts[0] has been a godsend when I want runtime type-checking on io boundaries (and you may have a lot of them in a front-end application).

[0]: https://github.com/gcanti/io-ts/blob/master/index.md#typescr...


These sorts of libraries are great in my experience. Here's a comparison of some of the more popular ones:

https://github.com/colinhacks/zod#comparison


Runtime checks are what really differentiates PHP's approach from other languages. It gives the type signatures teeth (ensuring they don't lie) and keeps dynamic typing under control (types enforced at function boundaries), which is really important in a language where many operators and functions will return one type most of the time and a different type the rest of the time. It also means type information can be relied upon by the compiler to produce more optimal code. In this respect it has a particular edge over TypeScript, which throws away all the types when producing JavaScript and thus the JS engine has to try to reconstruct them if it wants to do optimisations.


"the end result is the program crashing" is purely a byproduct of the type error being unintentional.

It should not be surprising that an attacker payload will be designed to ensure that the type error does not cause your service to crash (at least not immediately).

Additionally the type errors that you hit are likely fairly shallow: they're a product of using the wrong request in your generated user facing site which will show up pretty much immediately.

A dynamic language without the type checks has the memory safety properties of C (I think I could make an argument that it's worse)


I often think people don't have a problem with static types and instead would miss the implicit type conversion dynamic typing provides.


> miss the implicit type conversion dynamic typing provides

Implicit type conversion is not an inherent property of dynamic typing. For example, Python does not do implicit type conversion except in arithmetic. You can't do, e.g., `4 + "hello"`.

Leaving that aside, though... I'm actually not convinced implicit type conversions are much of a win, especially when it comes to types other than the simple arithmetic ones (and those can often be handled in explicit type-checking anyway, through a numerical type hierarchy). I would rather be forced to handle explicit conversion everywhere I want it than accidentally forget about some edge case and end up with an unintended implicit conversion that causes unexpected behavior at run time.


> Python does not do implicit type conversion except in arithmetic. You can't do, e.g., `4 + "hello"`

Interestingly, PHP uses separate operators for numerical addition (+) and string concatenation (.), so it's actually a bit more explicit than e.g. Javascript (where 'x + y' could result in normal numerical addition, or normal string concatenation, or implicit conversion to a numerical addition, or implicit conversion to a string concatenation!)


> Interestingly, PHP uses separate operators for numerical addition (+) and string concatenation (.), so it's actually a bit more explicit than e.g. Javascript (where 'x + y' could result in normal numerical addition, or normal string concatenation, or implicit conversion to a numerical addition, or implicit conversion to a string concatenation!)

Only because Rasmus didn't know how to design and implement a "proper" programming language. This isn't a intended feature, it just made parsing easier for him.


Rasmus borrowed this distinction from Larry Wall, since PHP was originally implemented in Perl (which also uses + for addition and . for string concatenation).

The neat thing about PHP and the + operator is that you can sometimes trick the interpreter into doing math on the underlying bytes of the string: http://dominic-mulligan.co.uk/wp-content/uploads/2015/05/SRE...


There's nothing improper about having a separate operator for concatenation.


> There's nothing improper about having a separate operator for concatenation.

Only to monopolize that operator to then have to use "->" for method call. C/C++ do that because of dereferencing. There is no need for that in PHP OO, all objects are passed by pointer to functions or methods.


Meh, other languages manage to assign multiple meanings to single tokens and depend on context to resolve ambiguity. PHP's issue here is that it refuses to do so, which is how we ended up with the backslash as a namespace delimiter.


I love Python because you can just do

    foo = "bar"
and be done with it. The interpreter knows it's a string variable. But I don't really care about runtime dynamic typing - I can't wait for Go to get parametric polymorphism, interfaces and runtime type dispatching can be quite clumsy. I would be perfectly fine with typed function arguments in a Pythonic language and being able to only call functions that fit the variable type (which is what I end up doing anyway with typehints and mypy).

I think Go made a right decision there - they kept the static typing but added a walrus := operator for automatic type inference. Every function has a type signature so the type inference algorithm is pretty trivial, and the language still feels like Python:

    result, err := FunctionThatMayFail()


Basic type inference has been around for decades, Go isn’t responsible for it. Haskell, scala, etc had it for a long time.


>Basic type inference has been around for decades, Go isn’t responsible for it.

I never said it was.


I think scala strikes a pretty good balance here. It's statically typed, but the compiler will attempt to infer types if not explicitly defined. If you want an implicit type conversion the type conversion function needs to be defined and in scope


those a pretty orthogonal issues, you can make a system arbitrarily static or dynamic with or without implicit conversion


Do you mean something like passing a string to a function and later passing a number and have it still act appropriately?


Welcome to JavaScript.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: