In general, I have both of the languages filed in my head as "Decent scripting languages with the risks associated with any-typed variables and soft coercion", but given those risks, I prefer Lua because it's simpler; there are just fewer abstractions and corner cases to trip over. Contrasting with JavaScript, where the sheer scope and complexity of the language (which has only grown as features have been added to try and avoid the sharp edges, which unfortunately only makes the sharp edges into legacy sharp edges, it doesn't eliminate them) means there are an uncounted number of ways to shoot yourself in the foot (https://www.destroyallsoftware.com/talks/wat).
That's actually my solution. I don't code directly in JS anymore; I always approach it from TypeScript. I do not have time to track down bugs resulting from typos (or brain burps) causing me to mis-assign values to the wrong variables, and static typechecking coupled with variables with immutable type specifications eliminates about 80% of those instances for me.
(Going that road, backwards compatibility in JS isn't a concern for me as a web app developer, because it's pushed into the realm of "Things compiler and browser writers care about." ;) )
Until right now, it never occurred to me to read this as a year. Would be a great campaign slogan.
That way, the existing web doesn't break, and people can start using a more sane JS as support rolls out over the course of 1-2 years.
How does this work? Internally it is implemented using a hashmap so somehow they must generate a hash value of the object? What if the object has member variables that are themselves objects? What if those things point to each other in a cycle?
> let m = new Map
> let o = {}
> m.set({}, 1)
Map { {} => 1 }
> m.set(o, 2)
Map { {} => 1, {} => 2 }
> m.get({})
undefined
> m.get(o)
2
or Python's dicts: >>> class A: pass
...
>>> a1 = A()
>>> a2 = A()
>>> {a1: 1, a2: 2, A(): 3}
>>> d = {a1: 1, a2: 2, A(): 3}
>>> d
{<__main__.A object at 0x107a9f1d0>: 1, <__main__.A object at 0x107a9f208>: 2, <__main__.A object at 0x107a9f2b0>: 3}
>>> d[A()]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: <__main__.A object at 0x107a9f240>
>>> d[a1]
1
>>> d[a2]
2
or Ruby's: > class A; end
> a1 = A.new
> a2 = A.new
> m = { a1 => 1, a2 => 2, A.new => 3}
=> {#<A:0x007fbe691ecdf8>=>1, #<A:0x007fbe691f4f08>=>2, #<A:0x007fbe6920efc0>=>3}
> m[A.new]
=> nil
> m[a1]
=> 1
> m[a2]
=> 2
Statically typed languages can more easily constrain keys[0], but even then the lure of all objects being hashable and equatable is strong e.g. I'm pretty sure all Java and C# objects are hashable and equatable (and can be used as hashmap keys out of the box).[0] Rust's hashmap requires that its keys implement Hash and Eq, neither of which are implemented by default; Haskell's Data.Map that its key be Ord; ...
It’s more about ergonomics than availability.
[0] https://docs.python.org/3/library/types.html#types.SimpleNam...
Ruby does the same, except that it's `[:'foo']` because Ruby has two string types because fuck you.
JavaScript? When talking about a language most refer to as "JavaScript-like", it's definitely a noteworthy feature. Otherwise, you're right, it's pretty much expected to be available. Although, I'd say beginner developers might forget about it since the typical use case has string or number keys.
Both Lua and Javascript attach protocols to their hashmaps which most languages attach to other bits (classes, typeclasses, traits, …) — even if those other bits are underlaid by hashmaps at the end of the day.
They're more like general-purpose objects which can act as hashmaps (badly in the case of javascript).