1

modern dynamic programming languages like Python, Ruby and Javascript all take the approach of treating everything as an object, what's the benefit of this approach, and what's the curse of it?

Benefit I can think of: first class functions, duck typing.

Drawbacks:performance issue, especially performance of primitive types like integer etc.

Boyu Fang
  • 121
  • 6
  • What do you mean by "as an object"? Are you referring to language semantics or implementation strategies? –  Jan 07 '15 at 16:28
  • @delnan I am referring to implementation strategies. – Boyu Fang Jan 07 '15 at 16:29
  • The JS implementations I'm aware of don't heap-allocate primitives (they use NaN tagging). –  Jan 07 '15 at 16:31
  • All currently available Ruby implementations, decidedly do *not* implement everything as an object. YARV, Rubinius, MRuby, and MacRuby, for example, use a tagged pointer representation that allows `Fixnum`s, `true`, `false`, `nil`, and in the case of YARV also flonums to be represented directly as their machine code representation. – Jörg W Mittag Jan 07 '15 at 16:32
  • @dorafmon You say "implementation strategies" but then refer to language semantics when you bring up first class functions and duck typing as benefits. You could probably write an inefficient interpreter for C in Python where everything is an object under the hood, yet C has neither feature. So what are you asking about? That aside, as far as I know first-class functions have nothing to do with dynamic typing or objects (except for the fact that you can encode objects as bundles of functions). – Doval Jan 07 '15 at 16:33
  • Well, in an object-oriented language first-class functions need to be objects, otherwise they wouldn't be "first-class". Then again, a first-class function is the same as an object with a single method (in fact, that's how it works in Python (everything with a `__call__` method is a function), Ruby (everything with `call` and `to_proc` methods is a function), Scala (everything with an `apply` method is a function), and Java (every instance of an interface or abstract class with a single abstract method regardless of its name is a function)). – Jörg W Mittag Jan 07 '15 at 16:37
  • @JörgWMittag Is that really the case? OOP languages can still have primitive types, and a function could still be a primitive type. (That's arguably a more useful view than starting with objects and saying functions are one-method objects, in the same sense that I find it awkward to say a variable is an array of length 1). I've always understood the *first class* part of *first class functions* to mean that functions can be bound to variables and passed around like any other type, not that they're considered objects in the context of an OOP language. – Doval Jan 07 '15 at 16:39
  • @Doval: I take a slightly broader view of "first-classness" than that. I always understood that Abelson-Sussman definition more as an example in the context of Scheme (where procedures and variables are all there is) than a language-agnostic definition. For me, a first-class element of a language is an element with which you can do anything and everything that you can do with every other element of the language. That certainly includes passing as an argument, returning as return value or binding to a variable (but note that not all languages have arguments, return values or variables), but … – Jörg W Mittag Jan 08 '15 at 05:04
  • … it can mean a lot of things as well. Java, for example, has type constructors. Type constructors can abstract over types … well, but not *all* types, only object types, not primitive types. So, that is one thing you cannot do with primitives that you can do with objects, so clearly primitives are second-class compared to objects, even though you can bind them to variables and return them from and pass them to methods. Primitives, to me, imply that they are in some way restricted. (Otherwise, they wouldn't be primitives, would they?) So, then, the notion that primitives are second-class … – Jörg W Mittag Jan 08 '15 at 05:07
  • … becomes a tautology: first-class means unrestricted, primitive means restricted, ergo primitives are by definition not first-class. (Note, however, that this only applies to *my* definitions of "first-class" and "primitive", however, also note that both Strachey who coined the term, and Abelson-Sussman who came up with the famous "rights and privileges" formulation are only talking about examples, not exhaustive definitions; specifically Abelson-Sussman say "**some** of the rights and privileges are …"). In particular, OO languages are all about manipulating objects, if something is not an … – Jörg W Mittag Jan 08 '15 at 05:15
  • … object, it is pretty much automatically not first-class. – Jörg W Mittag Jan 08 '15 at 05:15
  • @JörgWMittag Even if you use that definition of first-class, it seems strange to me to say that primitive means restricted. I've only ever seen it used in two ways: 1) an atomic type that can be used to define composite types, or 2) a type built into the language (but may be composite; e.g. `Object`). And that they happen to have restrictions in Java seems like an implementation issue to me. I imagine Java could've been designed such that `int` behaves in every way like a `sealed class`, with `+` just being syntax sugar, but still implemented as a machine word under the hood. – Doval Jan 08 '15 at 05:29

2 Answers2

5

It's not entirely clear whether you are talking about the semantics or the pragmatics here. Your question reads more about semantics, but in the comments you say you are asking about pragmatics. I'm going to answer about both.

Semantics

  • Pros: simplicity. Why have two concepts when one will do?
  • Cons: none.

Pragmatics

  • Pros: simplicity. Again.
  • Cons: Performance.

The Real World

The way this is used in the real world is that you have pure object-oriented languages whose implementations use primitives without ever exposing them to the programmer. So, the only people who need to deal with the complexities of having two different "things" (objects and non-object primitives) are the compiler/interpreter implementors. Language users simply use objects.

So, in other words, use semantics without primitives, but implement them with primitives. YARV, Rubinius, MRuby, MacRuby, pretty much all Lisp and Smalltalk (and derivatives such as Self and Newspeak) implementations, some ECMAScript implementations, and probably many others do this.

The techniques for doing this have been known since (at least) the 80s. Like many other useful things, the designers of the Java platform seem to have forgotten them when they designed the Java platform, and are now fighting a decades long uphill battle against backwards compatibility constraints to remove primitives again. (They are rumored to be gone by Java 10, but then again, this rumor has been going around for 15 years now.)

Jörg W Mittag
  • 101,921
  • 24
  • 218
  • 318
  • 1
    Primitives and objects are still leaky abstractions, and the programmer still has to be aware of the differences. How does one properly compare a string vs. a number in Java, for example? – Robert Harvey Jan 07 '15 at 16:58
  • @RobertHarvey I'd argue that the real leaky abstractions are the assumptions that 1) everything is comparable, 2) there's precisely one way to compare things, and 3) everything must be comparable against everything else. Point 1 is wrong since it doesn't make sense to compare functions (function equivalence is undecidable) or infinite data structures (never terminates.) Point 2 is wrong because you can compare integers by magnitude, divisibility, or absolute value. Point 3 is wrong since for `x` and `y` of different types, `x.equals(y)` might be true while `y.equals(x)` is not. – Doval Jan 07 '15 at 18:42
  • @Doval: Well, you can hide such differences. The `==` operator is overridden in C# for strings so that it does what you might reasonably expect it to: tell you if two strings have the same set of characters. Whether two strings have the same object reference is not particularly useful to most programmers in most contexts, especially when you consider string interning. – Robert Harvey Jan 07 '15 at 18:59
  • @RobertHarvey I'm not sure I get your line of reasoning. What I was getting at is that *"how do you compare an X to a Y?"* isn't a well-posed problem regardless of whether X or Y are primitives or objects. Back to your main point, you wouldn't need to be aware of the concept of primitive vs object if Java had a better implementation of generics. – Doval Jan 07 '15 at 19:28
  • Why would the Java folks care that much about removing the primitives? They don't appear to be an inherent, black-and-white flaw. – Panzercrisis Jan 07 '15 at 21:07
  • @Panzercrisis The problem isn't primitive types, it's that generics don't work with them. If you're writing performance-sensitive code in Java and boxing/unboxing causes a performance hit, you need to copy and paste any generic class/function once for every primitive. That's especially true when handling arrays, since there's a big difference between an array of integers and an array of pointers to Integer objects which may or may not be contiguous in memory. The whole point of generics is to not have to duplicate the code for every type! – Doval Jan 08 '15 at 00:04
  • @RobertHarvey: That's exactly my point. Having both primitives and objects in the language is needless complexity for absolutely no gain. There *is* a gain having primitives in the *implementation* but not in the language. In Ruby, for example, a `Float` is an object. Period. *However*, in YARV, when a `Float` can be exactly represented in 62 bits or less, it will be encoded as a machine float (called *flonum*), but that is completely transparent to the programmer. On 32 bit systems, YARV doesn't use flonums, and other implementations don't use them at all, but that doesn't matter, because … – Jörg W Mittag Jan 08 '15 at 05:21
  • … they are a private internal optimization detail and not part of the language spec. `Integer`s are handled similarly. In Ruby, `Integer`s are *true* arbitrary sized mathematical integers, but `Integer`s which fit into some small space (typically 31 bit on a 32 bit CPU, 63 bit on a 64 bit CPU, 64 bit on the JVM) are represented as primitives (called `Fixnum`s), and transparently overflow into `BigInteger`s (and back). These two are actually exposed to the programmer, which I consider to be mistake (but the spec only mentions that implementation-specific subclasses of `Integer` are allowed.) – Jörg W Mittag Jan 08 '15 at 05:27
  • @Doval: I think you misunderstood Robert. He isn't talking about comparing numbers with strings, he is talking about the fact that the operation for comparing two numbers is different from the operation for comparing two strings, and that using the same operation that you would use for comparing numbers to compare strings doesn't even give an error, but actually *does* work but yields wrong results. – Jörg W Mittag Jan 08 '15 at 05:30
  • @Panzercrisis: not having primitives has absolutely no downside, but a serious upside: simplicity. Conversely, from the other side, *having* primitives has absolutely no upside, but a serious downside: complexity. Not being able to abstract over primitive types, for example, is a serious downside. If I want to write an interface representing a function of two arguments, I can't write *one* interface `Function2`, I actually have to write a whopping 810(!) interfaces of the form `Function2_T1_T2_R`, `Function2_T1_byte_R`, `Function2_T1_short_R`, … – Jörg W Mittag Jan 08 '15 at 05:42
  • … and so on, for all 810 combinations of 8 primitive types plus 1 generic object type, squared (for two arguments) times 10 for the return type (the 9 from before plus `void`). (8 primitives + 1 object)*(8+1)*(8+1+1 `void`) = 810 interfaces for representing the simple concept of "function that takes two arguments". C#, in contrast, only has *2* interfaces: `Function` and `Action`. (Note that the second interface is only needed because `void` is not a first-class type. Once again, second-classness strikes.) Scala *has* a first-class type for representing no return value, … – Jörg W Mittag Jan 08 '15 at 05:46
  • … therefore it needs only one interface: `Function2[T1, T2, R]`, a function which doesn't return simply has `Unit` as its return type. – Jörg W Mittag Jan 08 '15 at 05:48
  • Doesn't C++ allow the same "generics", or technically templates, to be used on both primitives and objects? If not, would this still be a viable concept in language design? It seems like you mentioned performance with primtives. – Panzercrisis Jan 08 '15 at 13:50
1

Performance doesn't matter. Or rather: compared to providing developers the best tool with which they can express and model what they want the computer to do, performance is the easier problem to solve.

Types, classes, objects, functions, lambdas - all those concepts help organize your thoughts and your code. At this point, they're all quite unrelated to how the processor is going to actually execute your code. The 'curse', if there is one, is when your language of choice forces you into a particular model (classes vs object, of object vs functional) when you'd like to use another for a particular problem.

Regarding actual implementation, I don't know enough to comment about it. However consistency is valuable: having everything appear as an object to the developer might make implementing the language slightly harder, but makes using it much easier. It doesn't matter (in most cases) wether internally it has special treatment because it's an int or not.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
ptyx
  • 5,851
  • 2
  • 22
  • 21