forked from mrBliss/cpl-js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
semantics.txt
478 lines (399 loc) · 18.8 KB
/
semantics.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
SEMANTICS
=========
Q: I've heard that JavaScript is an object-oriented language, could you expand
on that?
A: Indeed, JavaScript is object-oriented. Typically, in object-oriented
languages, everything is an object (e.g. Python, Scala, Ruby, Eiffel, ...)
In JavaScript this is not the case, there are both objects and primitives.
The primitives in JS are numbers, strings, booleans, and the special types
null and undefined. The objects include Array, Function, Date, RegExp,
Error, ... This design decision, by many people considered a mistake, was
carried over from Java.
Objects in JavaScript are in fact containers of properties, where each
property has a name and a value. By assigning a function to a property's
value, one can add a method to the object. This demonstrates the importance
of the presence of first-class functions. Objects in JavaScript can be
compared with untyped hashmaps from strings to values from any kind. Then
name of a property can be any string, but the quotes can be dropped if the
name of the property is a valid JavaScript identifier. The quotes are
required when the property name is a reserved keyword, an empty string, or
other strings not constituting valid JavaScript identifiers.
```JavaScript
var emptyObject = {};
var person = {
name: "Erin",
age: 28,
"place-of-birth": "Gibraltar", // hyphens are not allowed in JavaScript identifiers
walk: function () { return "Do bi do ... walking, walking, walking"; }
};
```
Property lookup is done via the `.` notation. When the property name is not
a valid JavaScript identifier, one has to use the bracket notation. Looking
up a property of which the value is a function, just returns that function.
To actually invoke it, terminate the expression with `()`.
```JavaScript
person.name; // "Erin"
person["place-of-birth"]; // "Gibraltar"
person.walk; // [Function]
person.walk(); // "Do bi do ... walking, walking, walking"
```
This notation of objects is compact, easy to parse and parse, and has
become a popular general data-interchange format, called JSON (JavaScript
Object Notation).
TODO expand on JSON
Q: Object-oriented programming includes more than "everything is an object".
What about polymorphism, inheritance, etc.?
A: Of course, OOP is broader than "everything is an object". Most definitions
of object-oriented programming include features such as modularity,
encapsulation, polymorphism, inheritance, ...
JavaScript has support most of them, but differs from other classical OO
languages in the implementation of these features. Take for instance
inheritance. Classical OO languages mostly feature a class-based
inheritance mechanism, whereas JavaScript has prototype-based inheritance.
JavaScript often has a different way of doing things compared to other
languages, this will be a recurring theme.
Q: What entails prototype-based inheritance?
A: A better term would be prototype-based programming, as inheritance is
just one non-mandatory ingredient of this style of object-oriented
programming. Prototype-based programming can easily be explained by
comparing it with class-based programming.
In class-based programming, one defines classes. Each class consists of
fields and methods. To use a class, one creates a new object of the
class, which will have the fields and methods defined in the class.
Let's make an analogy: the class is the blueprint of the car, and the
objects are cars built from the blueprint. Now let's throw inheritance
in the mix. One can define a class that inherits from another class.
This new class will contain all the fields and methods of the parent
class, but can add fields and methods, and can also override fields and
methods.
In prototype-based programming, one defines objects. Each object will
consist of fields and methods, or in JavaScript's case, properties. The
whole concept of classes is removed from the equation. Instead of
defining a blueprint to build new cars with, one just builds a car. But
does this mean that we have to build every car from scratch, i.e.
(re)define the fields and methods for every objects we want to use? No,
to make a new object with the same fields and methods as another object,
we /clone/ the old object to get a new one. In other terms: we define a
new object with the old object as prototype. Inheritance is also done
via cloning, but after the cloning, the new object is extended with new
fields and/or methods, and existing fields and/or methods can also be
overriden. This new object can, in turn, be used as a prototype for
other objects.
Q: Could you show how it's done in JavaScript?
A: In JavaScript, objects can be created via object literal. In this
case, `Object` will be their prototype.
```JavaScript
var o = {foo: "bar"};
// Object.getPrototypeOf(x) returns the prototype of x
Object.getPrototypeOf(o); // {}, which is an empty Object
```
Another way to create objects in JavaScript, is via the `new`
keyword, which you probably know from C++, Java, etc. The `new`
keyword should be followed by a function call. The function after the
`new` keyword is called the constructor. Objects created this way
will have their constructor's prototype as their prototype.
```JavaScript
// Car is a constructor
function Car(make) {
this.make = make;
};
Object.getPrototypeOf(Car); // [Function: Empty] is the prototype of a function
// Define a new method for Car
Car.prototype.drive = function() {
println("Vroom");
};
var myCar = new Car("BMW");
Object.getPrototypeOf(myCar); // { drive: [Function] }, the prototype of Car
```
Notice that the name of the constructor function is capitalized, this
is convention, carried over from Java.
Q: If the naming is only a convention, how then differs a constructor
from a regular function?
A: It doesn't actually, it's the use of `new` that makes a regular
function act like a constructor. By prepending this keyword, a
couple of things happen.
* A new object is created, accessible via `this`.
* The constructor function is invoked on this object. TODO refer
to caller contexts.
* The prototype of the constructor function is stored as the
prototype of the new object.
* The newly created object is automatically returned at the end of
the function call.
Here's another example of inheritance in JavaScript.
```JavaScript
// Create a new object that inherits from obj
function inherit(obj) {
function f() {}; // Dummy constructor
// The new object's prototype is obj
f.prototype = obj;
// Make the new object
return new f();
}
// Create a new object from scratch
var a = {x: "a's x"};
// Create a new object by cloning an existing object
var b = inherit(a);
// Change the value of b's x property
b.x = "b's x";
// This change only affects b
a.x; // "a's x"
b.x; // "b's x"
```
Q: What are the pros and cons of prototype-based programming?
Prototype-based programming gives the programmer more flexibility and
freedom to define and redefine the fields and methods of objects
dynamically. Instead of focusing on building a brittle taxonomy of
classes, as in class-based programming, the focus lies on making a set
of objects with the desired behaviour, reusing them via cloning, and
modifying their behaviour when it's required. When building a taxonomy,
it is often the case that some class at the bottom of the chain
inherited some unwanted fields or methods, as it is not always possible
to classify everything in a consistent taxonomy. Prototype-based
programming gives you the freedom to clone and override the fields and
methods of other objects at will.
For example, you have a bunch of `Cat` objects, but you want one of the
cats, named Dexter, to make a different meow-sound. You can simply
change the `meow` method of Dexter, a single `Cat` object, without
affecting the others, or having to define a new subclass of `Cat`.
Having so much control over which fields and methods to /inherit/ allows
the programmer to do things not possible in class-based languages
without language support, for instance traits can easily be emulated
using prototype-based programming. Even classes can be and often are
emulated.
Let's show you how to emulate classes using prototypes:
```JavaScript
function Car(make) {
this.make = make;
};
Car.prototype.drive = function() {
println("Vroom");
};
// Constructor for the subclass SportsCar
function SportsCar(make) {
this.make = make;
};
// The prototype of SportsCar is a regular Car
SportsCar.prototype = new Car();
// We change the constructor, because we inherited Car's constructor
SportsCar.prototype.constructor = SportsCar;
SportsCar.prototype.driveFast = function() {
this.drive(); this.drive();
};
// Instantiate a new SportsCar
var mySportsCar = new SportsCar("BMW");
mySportsCar.driveFast();
// Vroom
// Vroom
```
The steps to create a subclass are often put together in a single
function, allowing easy class-based inheritance.
Traits are also easy to emulate, although we don't have access to the
super objects anymore. Abstract members can also be emulated, but will
not be checked at compile-time for presence.
```JavaScript
// The Trait we define
var HasTires = {
tiresOk: true,
puncture: function() {
this.tiresOk = false;
}
};
// Add the properties of the trait to the prototype of obj
var mixin = function(obj, trait) {
for (var prop in trait) {
obj.prototype[prop] = trait[prop];
}
};
// We'll continue with the SportsCar defined above
mixin(SportsCar, HasTires);
// The already defined mySportsCar now has the functionality defined in
// the HasTires trait
mySportsCar.puncture();
mySportsCar.tiresOk; // false
```
Of course, freedom comes with a price. One of the disadvantages are the
slower field and method accesses/dispatches and to a lesser extend the
memory overhead. Objects cloned from others object don't clone the
fields and methods. It would be wasteful to clone this reusable data.
Instead, the cloned object keeps track of its prototype. Whenever an
inherited field or method of the new object, i.e. a field or method
defined in the prototype (or a prototype of the prototype), is accessed,
the new object delegates the call to the prototype. This delegation will
go on until a definition is found in one of the ancestor prototypes. The
longer this /chain/ of prototypes, the costlier a field access or method
call.
Another disadvantage is that it becomes harder to figure out which
fields and methods an object has, and where they were defined. When
using classes, knowing the class of an object is enough to know which
fields and methods an object has (this, of course, includes looking at
superclasses). In JavaScript, properties can be added and removed from
objects at run-time.
http://en.wikipedia.org/wiki/Prototype-based_programming
TODO mention caller contexts p284, this
Another example of JavaScript's /exotic/ way of doing things is
encapsulation. Most classical OO languages support visibility modifiers to
control the accessibility of fields and methods by other classes. In
JavaScript, there are no such modifiers, but encapsulation is still
achievable, via the use of closures.
Example:
```Java:
class A {
private int counter = 0;
public void inc() {
counter++;
}
}
..
A a = new A();
a.inc(); // OK
a.counter; // Error: not visibile!
..
```
```JavaScript:
var a = (function () {
var counter = 0;
return {
inc: function() {
counter++;
},
};
})();
a.inc(); // OK
a.counter; // Error: not visible!
```
Q: What is a closure?
A: TODO p 310
Q: What are the advantages of implementing encapsulation via closures?
A: Actually, one could call it /emulating/ encapsulation via closures.
Because of the absence of encapsulation, JavaScript programmers are
required to implement encapsulation manually via closures. So one could
call it a disadvantage of JavaScript, although some people argue against
encapsulation.
With closures, the programmer has more control over encapsulation, but
has to do more work to achieve it.
Polymorphism in JavaScript is achieved via inheritance.
Example:
```JavaScript
// Superclass Animal
var Animal = function(name) { this.name = name; };
// Default implementation of makeNoise
Animal.prototype.makeNoise = function() { return "Silence"; };
// Subclass Dog
var Dog = function(name) { this.name = name; };
Dog.prototype = new Animal();
// Override makeNoise
Dog.prototype.makeNoise = function() { return "Bark"; };
// Subclass Turtle
var Turtle = function(name) { this.name = name; };
// Don't override makeNoise
Turtle.prototype = new Animal();
var animals = [new Dog("Pluto"), new Turtle("Jeffrey")];
for (var i = 0; i < animals.length; i++) {
println(animals[i].makeNoise());
}
// Bark
// Silence
```
Q: You mentioned that one can /override/ a method in JavaScript, is also
possible to overload a method in JavaScript?
A: To be clear, a method (or function) overloads another method if it has
the same name, but differs in the number of arguments or the types of
the expected arguments. The right method to call is chosen statically,
at compile-time. Overloading is a feature typically present in
statically typed languages. With JavaScript being a dynamically typed
language, the compile-time resolution that determines which method to
call is ruled out, as the types are not known in advance.
Overloading based on the number of arguments is possible in dynamically
typed languages, but not in JavaScript. The reason being that JavaScript
doesn't check whether a function is called with the correct number of
arguments. JavaScript will happily allow you to call a function
expecting two arguments with 0, 1, 2, 3, ... arguments. Missing
arguments will be `undefined`. JavaScript also allows you to access the
excess arguments. The `arguments` object is a local variable accessible
within all functions. The `arguments` object is similar to an Array, but
not quite the same. The elements of `arguments` can be accessed, and it
has the `length`-property, the other methods available for `Array`s
cannot be used on the `arguments` object. One can convert the
`arguments` object to an actual array, by calling `Array`'s
`slice`-method on it, like so: `var args =
Array.prototype.slice.call(arguments);`. FOOTNOTE: The `slice`-method of
`Array` copies the elements into a new array, it optionally takes a
start- and end-indices.
Source: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments
Q: What's the point of this `arguments` object? And why isn't it a
proper `Array`?
A: The `arguments` object allows the programmer to define methods
expecting a variable number of arguments.
Example:
```JavaScript
// Sums all its arguments. The argument list is empty, because we'll
// use the arguments object.
function addAll() {
var args = Array.prototype.slice.call(arguments);
var total = 0;
// args.shift() removes the first element from the array, and
// returns that element.
while (args.length > 0) total += args.shift();
return total;
}
```
Even when expecting a fixed number of arguments, the programmer can
benefit from access to the `arguments` object. For instance when
implementing a decorator.
```JavaScript
function decorate(toDecorate, decorator) {
return function() {
// Both the decorator and the function to decorate can be
// called with the same arguments.
decorator.apply(null, arguments);
return toDecorate.apply(null, arguments);
};
};
function argsPrinter() {
var args = Array.prototype.slice.call(arguments);
println("Arguments: " + args.join(", "));
};
function add(x, y) { return x + y; };
decorate(add, argsPrinter)(1, 2);
// Arguments: 1, 2
// => 3
```
JavaScript's loose call convention, and the presence of the `arguments` object
allows the programmer to emulate overloading by doing the resolution at
run-time.
Example:
```Java
void overloaded() { return "No args"; }
void overloaded(int x) { return "A number"; }
void overloaded(String x) { return "A string"; }
void overloaded(int x, String y) { return "A number and a string"; }
```
```JavaScript
function overloaded(arg1, arg2) {
// One could also use an if-then-else construction
switch (arguments.length) {
case 0: return "No args"; break;
case 1: switch (typeof(arg1)) {
case 'number': return "A number"; break;
case 'string': return "A string"; break;
}
break;
case 2: if (typeof(arg1) == 'number' && typeof(arg2) == 'string')
return "A number and a string";
}
}
// Identical for the Java program
overloaded();
// No args
overloaded(1);
// A number
overloaded("foo");
// A string
overloaded(1, "foo");
// A number and a string
```
These `return`-statements could of course be replaced with different
blocks of code, specific for each case.
TODO Modularity in JavaScript p 306
TODO something about p 328
TODO functional programming p 331