8
$\begingroup$

I noticed in JavaScript and Lua, 'types' i.e., those returned by typeof()/type() are just identified by strings. As such we see code such as:

if (typeof(x) != "number") { /* ... */ }

Why would one do this instead of having specific objects that refer to types? Number as a constructor already exists in JavaScript so why not this instead?

if (typeof(x) != Number) { /* ... */ }

What are the reasons to use a string to identify types, instead of having a metatype (a 'type' type), or simply returning an object such as a constructor function?

$\endgroup$
2
  • 1
    $\begingroup$ At least in JavaScript, there’s a difference between primitive numbers and number objects, so if typeof returned the constructor it wouldn’t differentiate those. $\endgroup$
    – Bbrk24
    Jun 3, 2023 at 20:22
  • 1
    $\begingroup$ @Bbrk24 Then it could return Object for number objects, and Number for number primitives. $\endgroup$
    – CPlus
    Jun 3, 2023 at 20:24

1 Answer 1

8
$\begingroup$

For the scripting languages that do this, strings are one of the few built-in types and have a major advantage in being printable and introspectable. This is particularly valuable for languages meant to be embedded in other systems, like both Lua and JavaScript, where just getting a string displayed somewhere is a critical debugging step. That is a key usage of typeof/type(): my code is breaking, so tell me, what kind of value did I actually get at this point? This is also the case for PHP, and for R as well — they're really debugging tools more than anything else.

Adding additional meta-type objects is a pretty significant addition for fairly little benefit in that case, but when they do it would arguably be better to reverse the process anyway: expose a static Number.isNumber(x) rather than having people write typeof x == "number" at all. The ability to use the type identification for type-based branching is more of a side effect than a goal.

However, I'm not sure I agree that "so many dynamically-typed languages" do this. Other than the ones listed, widespread dynamically-typed languages designed as programming languages mostly don't seem to do this. Python and Ruby expose class objects instead, for example, and Tcl doesn't expose this at all, while in Perl it doesn't really even make sense as a question (perhaps ref() counts?).

On the other hand, if I'm building one I'm probably putting in strings before I consider reflective behaviour, so they have the advantage of availability. Many little languages just don't have anything beyond simple types that could represent this information. Adding something to describe a value to me for debugging early on in development and having it stick around is very plausible.


Let's take the JavaScript case as a working example. To recap, the typeof operator returns one of the strings "string", "number", "function", "object", "boolean", "object", "undefined", (new) "symbol", (newer) "bigint", or another host-defined string (not attested in practice). All non-primitive values return "object", except for functions; symbols are primitive types despite having reference identity and no literals. There are also boxed types String, Number, Boolean, and BigInt, which autounbox as needed; typeof new Number(1) returns "object". There is no constructor for the undefined type. typeof was introduced during the LiveConnect era of Netscape 3, as part of tighter connections with Java, and isn't part of the original "ten days in May".

A significant advantage of returning a string for an interactive scripting language like this is that the value is a string, so while debugging you can print it, or (more likely in 1996) alert(typeof x) it.

The boxed Number constructor is a function, as all JavaScript constructors are. If typeof returned that instead of a string, alert(typeof x) would display

function Number() {
    [native code]
}

because that is how native functions stringify. While this is eventually understandable, probably, it's definitely worse. This is a concrete example of where a direct string is more helpful, although of course the language could also have decided to specify toString() on this function to return something more useful, and perhaps would have done if it were expected to be used in this way.

JavaScript does provide a way of getting more useful information for reference types, by way of the constructor property on new-created objects, and Object.getPrototypeOf in the library. These would all be described just as "object" by typeof, but real inspectable objects are given by these operations. A modern JavaScript might well only have these, and no primitive types (for that matter, the original JavaScript probably would have done given more time and less pressure from Sun).

Lua has essentially the same set of types, where "object" is spelled "table". It has no real concept of data type beyond that, so adding a substantive meta-type object would have been a pretty big addition for very little gain. There's certainly an argument that a string is less good than some meta-object, and (as ever) that JavaScript got it wrong before cementing it as unchangeable.

A from-scratch design for JavaScript today might well make a different choice, though I suspect Lua wouldn't. If you're making a new language and you have support for a type of value that could represent these types better, I'd probably use it. If it's just for little embedded scripts and you only have a few core data types, then if you need to expose a typeof operation then strings seem like the clear choice.

$\endgroup$
1
  • 1
    $\begingroup$ Also consider that when represented as strings, future types can easily be added. If each type is represented by a reserved word, how can any new type be added without possibly conflicting with existing user code that shares the same namespace? $\endgroup$ Jun 4, 2023 at 12:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .