Hacker News new | ask | show | jobs
by Non24Throw 2245 days ago
I think in language design there’s an expectation that if an expression or statement doesn’t make any sense, then people won’t write it that way.

I think that’s a pretty reasonable expectation, too.

In JavaScript, {} + [] evaluates to integer 0. That doesn’t make any sense, but it makes more sense after reading the ES spec for the addition operator.

There are many expressions you can write in dynamically typed languages that don’t make any sense, probably most of them actually, but they have to be considered valid because it’s a dynamically typed language. So they’re valid, they will evaluate to something.

The language designers aren’t so concerned with identifying every possible combination that makes no human-intuitive sense. The important part is that when it seems like types should be inferred and coerced in a particular way, then that’s how it should work. It should match human intuition.

I don’t have any intuition or opinion about how True == False is False should be evaluated, this kind of thing is going to receive superfluous parentheses from me every time for the benefit of the reader, and if someone else wrote it this way I’m always going to look it up or test it in a REPL...

10 < x <= 100 though, if that’s considered a valid expression and it doesn’t evaluate to true for numbers in the range (10,100], I’m going to stop using that language...

3 comments

>In JavaScript, {} + [] evaluates to integer 0. That doesn’t make any sense, but it makes more sense after reading the ES spec for the addition operator.

It's the consequence of ill-thought mechanics when it comes to type coercion. It's the original sin of many scripting languages: "let's just add a bunch of shortcuts everywhere so that it doesn't get in the way of the developer trying to make a quick script". Then you end up with a bunch of ad-hoc type coercion and the language guessing what the user means all over the place, and eventually these bespoke rules interact with each other in weird ways and you end up with "{} + [] = 0".

> I think in language design there’s an expectation that if an expression or statement doesn’t make any sense, then people won’t write it that way.

That's either very idealistic or very naive. In either case I'd argue that's a terrible way to approach language design. I'd argue that many well designed languages don't make any such assumptions.

>but they have to be considered valid because it’s a dynamically typed language.

Nonsense. Try typing `{} + []` in a python REPL. You seem to be suffering from some sort of Javascript-induced Stockholm syndrome, or maybe simply lack of experience in other languages. JS does the thing it does because it was designed(?) that way, not because there's some fundamental rule that says that dynamically typed languages should just do "whatever lol" when evaluating an expression.

I don’t write JavaScript or have a strong opinion about it.

The widespread prevalence of JS transpilers and everyone’s apparent unwillingness to write pure JS makes me think it probably isn’t such a great language. It probably never had a chance given its history with browsers.

Just using it to make the point that every language has valid expressions that make no sense. You can find similar examples in every language. All you have to do to find them is start writing code in a way that no person ever would or should write it.

Especially in the case of dynamically typed languages, throwing an exception sometimes but not always based on the “human intuitiveness” of any given type inference would make the language even more unpredictable. It’s just instead of asking why expression a evaluates to x, we would all be asking why expression a evaluates to x but similar expression b throws an exception.

If you ask me, the latter is even more arbitrary.

These aren’t useful criticisms. The only reason these kind of critiques even get so much attention is because people reading the headline think: “I wonder how the hell that would be evaluated?” And so they click.

The headline is only interesting to begin with because nobody ever writes that, and nobody should ever write that.

If nobody would ever write it or have any expectations about its evaluation, then how is it even significant that the language will interpret it one way vs another?

I think these criticisms are a good springboard to have the debate about static vs dynamic typing, but arguing over whether True == False is False should be evaluated one way vs another is kind of pointless. If the result of that argument is agreement, then we might end up with people actually writing this, which should be the last thing any of us want.

Python, C, Go, and even Haskell have wtfs too where language features collide. Every complex language does, as a consequence of complexity too.
> There are many expressions you can write in dynamically typed languages that don’t make any sense, probably most of them actually, but they have to be considered valid because it’s a dynamically typed language.

Not really - a language could throw an exception in these cases, as Python does for things like “a”+1. Not every dynamically-typed language is JavaScript.

That is the difference between a dynamically typed language (like Python) and an untyped language (like JS). The dynamically typed language checks types at runtime, whereas the untyped language doesn't.
JS is not "untyped". It literally has a "typeof" operator. You probably mean strongly vs. weakly typed. JS is dynamically, rather weakly typed. Python is dynamically, strong-ish-ly typed. C is statically, weakly typed. Rust is statically, strongly type.

Those two attributes are mostly orthogonal.

There's no such thing as "strongly" or "weakly" typed languages. Those terms have no definition and are meaningless. Yes Wikipedia will tell you that C and JS are weakly typed, but what attribute is it that C and JS's type systems share? They share absolutely nothing in common.

Dynamic typing refers to checking of types at runtime. JS does not do this consistently or effectively. Python does.

The typeof operator cannot be the basis of a type system since you can count all the answers it gives on your fingers. JS needs a bunch of extra functions like Array.isArray() to determine if something is an array or not. The language itself has no clue, everything that isn't a primitive is just an "object" as far as JS is concerned.

"Strongly" and "weakly" typed is not a binary attribute of a language and there's a gradient so languages can be more or less strongly or weakly typed but I think those are still useful qualifiers. C lets you add ints and doubles without explicit cast, Rust doesn't. Rust is more strongly typed than C. It's not meaningless to say that. A metric being partially relative or interpretative doesn't make it useless.

For the rest I don't understand how anybody can seriously make the argument that JS has no types. Shell scripts have no types because almost everything is a character string and that's it. JS type system is very limited but it does exist and I don't see how it can be used to justify {} + [] = 0, which is where we started (especially given that {} and [] are objects in JS, but 0 is a "number", so different core types). Adding two objects and getting a number is not a limitation of the type system, it's a conscious decision by the designers (or at least the side effect of one).

Python (and Ruby) are dynamically typed and strong typed. They are two different things. Strong typed doesn't mean that you can't do this

  a = "a"
  a = 1
That's dynamic typing. Strong typing means only that

  a = "a" + 1
fails.

More about that at https://en.hexlet.io/courses/intro_to_programming/lessons/ty...

JavaScript and PHP and Perl automatically cast to a reasonable value with all the advantages and pitfalls.

>Strong typing means only that

> a = "a" + 1

>fails.

String a = "a" + 1; works in Java. So Java is not strongly typed?

In some ways yes, Java is not so strongly typed; a statically-typed language can be weakly typed.

See C where ints, pointers, floats, and bools can almost all coerce into each other, such that the compiler will allow you to use arithmetic/logic operators with most different types, whether you meant to or not.

That’s because the language designers overloaded the arithmetic operator (+) to perform string concatenation when the operands can be cast as Strings.

Without overloading the + operator, string concatenation which is a common operation, would have been unnecessarily verbose. Your example without the + operator would look like below:

   String a = new String(“a”).concat(1); // String object concat
   String a = “a”.concat(1);             // String literal concat
The same could be said of JS but for some reason it gets shit but Java gets off Scott-free.
It’s really not dynamic vs untyped, but the extent to which the language design chooses to coerce a value into a given type instead of raising a runtime type error. Python is guilty of this too with its truthy values, e.g. `if []` — an empty list of type `list` is suddenly apparently inhabiting the bool type and is equivalent to False.
> In JavaScript, {} + [] evaluates to integer 0.

This not really true. Put this js console and you'll see that a is the string "[object Object]":

  a = {}+[]
When you put just {}+[] in your console its doing an empty block followed by unary-plus,like:

  {/*do nothing block*/}; +[]
Huh that’s quite interesting. It behaves this way even when you explicitly make it an object literal rather than a block.

I guess JS’s parser explicitly prohibits adding things to a curly-brace enclosed entity?

I’m normally quite defensive of JS, but I’ll have to admit I don’t like that.

How did you explicitly make it into an object literal?

    ({}) + []
    -> "[object Object]"
will correctly make it "[object Object]"

Did you try to do this?

    {a: 1} + []
    -> 0
That's interpreted as the statement 1 labeled a, not as an object. This makes it obvious it's not an object:

    {a: 1, b: 2} + []
    -> Uncaught SyntaxError: Unexpected token ':'
JavaScript supports labels so you can continue/break multiple levels at once:

    {
      a: while (true)
        while (true)
          break a;
    }
Yes, I realized after the fact that I was indeed making a label. I’m now back to not having an issue with JS
It's just ASI (automatic semicolon insertion) and at the top-level {} declares a block not an object.

You can also do like ({} + []) and it will actually be "adding" them; the confusion solely comes from people typing into the JS console something that wouldn't make any sense to put into an actual script.

You’re right! I would never have guessed that. Learn something new every day.