Last active
January 24, 2018 04:03
-
-
Save ewinslow/b1667290f1134fdf0ee0 to your computer and use it in GitHub Desktop.
Revisions
-
ewinslow revised this gist
Jan 2, 2015 . 1 changed file with 31 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -164,6 +164,37 @@ function Animal(legs) { } ``` Or perhaps: ```js function Animal(legs) { this.move = function move(speed) { return legs.move(speed); }; this.walk = function walk() { return move('slow'); }; } ``` Or maybe even... ```js function Animal(legs) { let move = (speed) => legs.move(speed); let walk = () => move('slow'); return { get isRunning() { return legs.speed == 'fast'; }, move, walk, }; } ``` Which in itself isn't too bad, except that you have to repeat the names of public reference in order to avoid using "this" in any method bodies... -
ewinslow revised this gist
Jan 2, 2015 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -236,7 +236,7 @@ Here is an example if we were to equivalent for function return type annotations ```js class Dog(legs: Legs, voiceBox: VoiceBox): Animal { super(legs) // super call must be first isPanting:boolean = false @@ -245,6 +245,7 @@ class Dog(legs: Legs, voiceBox: VoiceBox): Animal { move(speed): boolean { isPanting = speed == 'fast'; // can only call current super method return super(speed); } -
ewinslow revised this gist
Dec 31, 2014 . 1 changed file with 1 addition and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -84,8 +84,7 @@ For example, ES6 classes will look something like this: ```js class Animal { // Type each parameter no less than 3x... :( constructor(legs) { this.legs_ = legs; } -
ewinslow revised this gist
Dec 31, 2014 . 1 changed file with 2 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -84,9 +84,8 @@ For example, ES6 classes will look something like this: ```js class Animal { // Type each parameter no less than 3x... yikes // Imagine a constructor with 3-4 params... constructor(legs) { this.legs_ = legs; } -
ewinslow revised this gist
Dec 31, 2014 . 1 changed file with 3 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -84,6 +84,9 @@ For example, ES6 classes will look something like this: ```js class Animal { // UGH -- type legs 4 times just to initialize the class? maddening... /** @param {Legs} legs */ constructor(legs) { this.legs_ = legs; } -
ewinslow revised this gist
Dec 31, 2014 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -30,10 +30,11 @@ The main benefit of the function-closure approach is truly private instance vari The prototype-based syntax attempts to mimic privacy by introducing Symbol, but even this is not enough because as far as I can tell, the latest spec still makes symbols enumerable, so private state can be accessed and modified by reflection, which kind of defeats the point of making something private. I think there is a strong possibility that we could get the best of both worlds here from a class syntax that resembles the function-closures approach: * Truly private variables * An optimizable syntax that *could* have similar memory-consumption traits of the prototype syntax -- haven't looked into this, but those folks are clever so I'm betting they could come up with something... * Much terser: fewer keywords, less ceremony, less boilerplate Example: -
ewinslow revised this gist
Dec 31, 2014 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -28,7 +28,7 @@ function Foo() { The main benefit of the function-closure approach is truly private instance variables. Notice that it's also *much* more terse, even without any special syntax for classes (especially since we can use ES6 arrow functions inside the closure and they work as expected). The prototype-based syntax attempts to mimic privacy by introducing Symbol, but even this is not enough because as far as I can tell, the latest spec still makes symbols enumerable, so private state can be accessed and modified by reflection, which kind of defeats the point of making something private. I think there is a strong possibility that we could get the best of both worlds here from a terse class syntax that resembles the function-closures approach: -
ewinslow revised this gist
Dec 31, 2014 . 1 changed file with 5 additions and 10 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -28,30 +28,25 @@ function Foo() { The main benefit of the function-closure approach is truly private instance variables. Notice that it's also *much* more terse, even without any special syntax for classes (especially since we can use ES6 arrow functions inside the closure and they work as expected). The prototype-based syntax attempts to mimic privacy by introducing Symbol, but even this is not enough because as far as I can tell, the latest spec still makes symbols numerable, so private state can be accessed and modified by reflection, which kind of defeats the point of making something private. I think there is a strong possibility that we could get the best of both worlds here from a terse class syntax that resembles the function-closures approach: * Truly private variables * An optimizable syntax that *could* have similar memory-consumption traits of the prototype syntax -- haven't looked into this, but those folks are clever so I'm betting they could come up with something... Example: ```js // There is no separate definition for the constructor function. // Instead the constructor arguments are listed after the class identifier class Dog(legs, voiceBox) { // Assignments are allowed and this is sugar for declaring *private* variables speed = 'stopped'; // Instance methods are defined identically to the existing proposal // ...but they have access to all private variables // They are public by default, contrary to property assignments shown above bark() { return voiceBox.makeSound('bark') } -
ewinslow revised this gist
Dec 31, 2014 . 1 changed file with 46 additions and 19 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,54 +4,81 @@ ES6 classes are happening, and there's no way the syntax is going to change radi ## Background The main premise of the [currently accepted syntax](https://github.com/esnext/es6-class) is to be simple sugar over the prototype style of class definitions: ```js function Foo() { this.bar_ = 'baz'; } Foo.prototype.doSomething = function() { return this.bar_ + 'bop'; }; ``` This is a very common way to write classes in JS and is more memory-efficient than its cousin, the 'function-closure' approach: ```js function Foo() { var bar_ = 'baz'; this.doSomething = () => bar_ + 'bop'; } ``` The main benefit of the function-closure approach is truly private instance variables. Notice that it's also *much* more terse, even without any special syntax for classes (especially since we can use ES6 arrow functions inside the closure and they work as expected). I think there is a strong possibility that we could get the best of both worlds here from a terse class syntax that resembles the function-closures approach: * Truly private variables * An optimizable syntax that could have similar memory-consumption traits of the prototype syntax. Example: ```js // There is no separate definition for the constructor function. // Instead the constructor arguments are listed after the class identifier class Dog(legs, voiceBox) { // Assignments are allowed and this is sugar for declaring private variables speed = 'stopped'; // There is no facility executing other arbitrary statements // This forces the constructor to be without side effects // If you want construction with side effects, you can define // a factory function on the class: // Dog.create = function(legs, voiceBox) { var dog = new Dog(legs, voiceBox); dog.bark(); return dog; } // This may be too restrictive for folks, but I find it actually leads to better-designed code... // Instance methods are defined identically to the existing proposal // ...but they have access to all private variables // They are public by default, contrary to property assignments bark() { return voiceBox.makeSound('bark') } // "public" properties are exposed via getters // They can be made writable by declaring a corresponding setter get isPanting() { return speed == 'fast'; } // Methods have access to all other methods without needing to use this.* run() { move('fast') } // Arguments to methods are defined as normal. move(newSpeed) { speed = newSpeed; legs.move(newSpeed); } } ``` ## Detailed explanation Most class syntaxes I've ever seen are implemented by specifying one specially-named method to be the constructor, e.g. __construct or constructor or {NameOfClass}. This is bad because: * It is syntactically awkward. Constructors are not methods. They shouldn't be defined like one. * They require you to manually store references to private variables. Constructors for well-designed classes just take their arguments and assign them to private variables. This mind-numbing boilerplate gets maddening quickly. -
ewinslow revised this gist
Dec 31, 2014 . 1 changed file with 31 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,9 +1,36 @@ ## Introduction ES6 classes are happening, and there's no way the syntax is going to change radically just for me this late in the game, but I still figure what the heck. Maybe I can write a language someday that has this feature. Or maybe I'm wrong and the feature can change with enough motivation... ## Background The main goal for the [currently accepted syntax](https://github.com/esnext/es6-class) is to be simple sugar over the prototype style of class definitions: ```js function Foo() { this.bar = 'baz'; } Foo.prototype.doSomething = function() { return this.bar += 'bop'; }; ``` This is a very common way to write JS and is more memory-efficient than its cousin, the 'closure-style' approach to defining classes. ```js function Foo() { this.bar = 'baz'; this.doSomething = function() { return this.bar += 'bop'; }; } ``` The main benefit of the closure-style approach is truly private member variables. I think there is a possibility that we could get the best of both worlds here from a terse class syntax that sugars the closures approach. Truly private variables and an optimizable syntax that has similar memory-consumption traits of the prototype syntax. Quick example: -
ewinslow renamed this gist
Dec 31, 2014 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,5 @@ ES6 classes are happening, and there's no way the syntax is going to change radically just for me this late in the game, but I still figure what the heck. Maybe I can write a language someday that has this feature. Or maybe I'm wrong and the feature can change with enough motivation... Features: * real privacy * no need for weak maps, symbols, or extra keywords @@ -23,8 +25,6 @@ class Dog(legs, voiceBox): Animal { } ``` Most class syntaxes I've ever seen are implemented by specifying one specially-named method to be the constructor, e.g. __construct or constructor or {NameOfClass}. This is bad because: * It is syntactically awkward. Constructors are not methods. They shouldn't be defined like one. * They require you to manually store references to private variables. Constructors for well-designed classes just take their arguments and assign them to private variables. This mind-numbing boilerplate gets maddening quickly. -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 20 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,6 +3,26 @@ Features: * no need for weak maps, symbols, or extra keywords * very terse Quick example: ```js class Dog(legs, voiceBox): Animal { super(legs) isPanting = false bark() => voiceBox.makeSound('bark') move(speed) { isPanting = speed == 'fast'; return super(speed); } run() => move('fast') } ``` ES6 classes are happening, and there's no way the syntax is going to change radically just for me this late in the game, but I still figure what the heck. Maybe I can write a language someday that has this feature. Most class syntaxes I've ever seen are implemented by specifying one specially-named method to be the constructor, e.g. __construct or constructor or {NameOfClass}. This is bad because: -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 5 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,8 @@ Features: * real privacy * no need for weak maps, symbols, or extra keywords * very terse ES6 classes are happening, and there's no way the syntax is going to change radically just for me this late in the game, but I still figure what the heck. Maybe I can write a language someday that has this feature. Most class syntaxes I've ever seen are implemented by specifying one specially-named method to be the constructor, e.g. __construct or constructor or {NameOfClass}. This is bad because: -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 8 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -136,6 +136,14 @@ class Animal(legs) { This is OK, but it's limiting because you can't do any transformations on the private state before it is returned (e.g. cloning or returning an iterator instead of the array itself). It's not extensible. So once you want to do anything fancy, you'd have to move to getters/setters anyways. It's even worse for setters because it's even more likely you might want custom code to run in a setter. So it seems like `public const foo` wouldn't actually be shorthand and is more limiting than getters/setters. What about private methods? Don't need a special syntax or keyword, just assign a function to a private member variable: ```js class(bar) { foo = () => bar.baz() } ``` I'm torn whether to even define class inheritance. But I suppose its used often enough that people want it... ```js -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 3 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -33,7 +33,9 @@ class Animal { } ``` This is better but you can see that we still have to store references to the injected services on the instance. Underscore is meant to denote private, but this is a lie because in JS assigning `this.*` always makes the variable a publicly accessible member of the class. You need a tool to check for inappropriate accesses for you, which isn't great because it isn't enforced in uncompiled code. In my experience this situation commonly leads to bad code like: * Tests that access "private" state (because it's actually public). -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 1 addition and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,6 +3,7 @@ ES6 classes are happening, and there's no way the syntax is going to change radi Most class syntaxes I've ever seen are implemented by specifying one specially-named method to be the constructor, e.g. __construct or constructor or {NameOfClass}. This is bad because: * It is syntactically awkward. Constructors are not methods. They shouldn't be defined like one. * They require you to manually store references to private variables. Constructors for well-designed classes just take their arguments and assign them to private variables. This mind-numbing boilerplate gets maddening quickly. * It's one more thing to name and type out. For example, ES6 classes will look something like this: -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 7 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -151,12 +151,16 @@ Here is an example if we were to equivalent for function return type annotations class Dog(legs: Legs, voiceBox: VoiceBox): Animal { super(legs) isPanting:boolean = false bark(): boolean => voiceBox.makeSound('bark') move(speed): boolean { isPanting = speed == 'fast'; return super(speed); } run(): boolean => move('fast') } ``` -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 18 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -139,12 +139,29 @@ I'm torn whether to even define class inheritance. But I suppose its used often class Animal(legs) extends LivingThing {} ``` extends is another keyword and you know how I feel about keywords by now... they bloat the source. JS doesn't need bloat. Instead, we could use `:`, like the proposed type declaration syntax for AtScript, which seems like it makes sense since inheritance is a kind of type information as well. ```js class Animal(legs): LivingThing {} ``` Here is an example if we were to equivalent for function return type annotations and parameter type hints: ```js class Dog(legs: Legs, voiceBox: VoiceBox): Animal { super(legs) fleas = [] hasFleas(): boolean => !!fleas.length bark(): boolean => voiceBox.makeSound('bark') run(): boolean => move('fast') } ``` I think my proposal is better than the status quo because: * It's about as terse as you can possibly get. No extraneous keywords for (`this`, `function`, or even `return` in simple cases). -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -131,6 +131,8 @@ class Animal(legs) { } ``` This is OK, but it's limiting because you can't do any transformations on the private state before it is returned (e.g. cloning or returning an iterator instead of the array itself). It's not extensible. So once you want to do anything fancy, you'd have to move to getters/setters anyways. It's even worse for setters because it's even more likely you might want custom code to run in a setter. So it seems like `public const foo` wouldn't actually be shorthand and is more limiting than getters/setters. I'm torn whether to even define class inheritance. But I suppose its used often enough that people want it... ```js @@ -143,8 +145,6 @@ extends is another keyword and you know how I feel about keywords by now... they class Animal(legs): LivingThing {} ``` I think my proposal is better than the status quo because: * It's about as terse as you can possibly get. No extraneous keywords for (`this`, `function`, or even `return` in simple cases). -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 13 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -109,7 +109,7 @@ function Animal(legs) { ``` So variable assignments are always locally-scoped and private. It's not possible to reassign global variables in a class constructor, or to expose public variables... This is a little bit of me exposing my personal coding philosophy, but I find public member variables to be an anti-pattern. We have getters/setters in JS so they're not really necessary anyways. Getters/setters can give you "read-only" access (by simply not defining a setter): ```js class Animal(legs) { @@ -131,6 +131,18 @@ class Animal(legs) { } ``` I'm torn whether to even define class inheritance. But I suppose its used often enough that people want it... ```js class Animal(legs) extends LivingThing {} ``` extends is another keyword and you know how I feel about keywords by now... they bloat the source. JS doesn't need bloat. Instead, we could use `:`, like the proposed type declaration syntax for AtScript, which seems like it makes sense since inheritance is related to types. ```js class Animal(legs): LivingThing {} ``` This is OK, but it's limiting because you can't do any transformations on the private state before it is returned (e.g. cloning or returning an iterator instead of the array itself). It's not extensible. So once you want to do anything fancy, you'd have to move to getters/setters anyways. It's even worse for setters because it's even more likely you might want custom code to run in a setter. So it seems like `public const foo` wouldn't actually be shorthand and is more limiting than getters/setters. -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -113,9 +113,9 @@ So variable assignments are always locally-scoped and private. It's not possible ```js class Animal(legs) { moves_ = []; get moves() => moves_ // ... } -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 1 addition and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -24,6 +24,7 @@ class Animal { Dart has tried to alleviate this constructor situation by allowing `this.*` to appear in the constructor which automatically does assignments. It would end up looking something like this in JS: ```js class Animal { constructor(this.legs_) -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 99 additions and 29 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -8,67 +8,137 @@ For example, ES6 classes will look something like this: ```js class Animal { constructor(legs) { this.legs_ = legs; } move(speed) { return this.legs_.move(speed); } walk() { return this.move('slow'); } } ``` Dart has tried to alleviate this constructor situation by allowing `this.*` to appear in the constructor which automatically does assignments. It would end up looking something like this in JS: class Animal { constructor(this.legs_) // ... } ``` This is better but you can see that we still have to store references to the injected services on the instance. This is a little backwards because in JS assigning `this.*` always makes the variable a publicly accessible member of the class. You need a tool to check for inappropriate accesses for you, which isn't great because it isn't enforced in uncompiled code. In my experience this situation commonly leads to bad code like: * Tests that access "private" state (because it's actually public). * Collaborating classes that reach into private state of a collaborator class. * A private member variable of a subclass can accidentally override that of the super class, causing bugs. * etc... Instead, if we used the existing function scoping of ES5, we get true privacy and the class definition would gets more compelling IMHO: ```js class Animal(legs) { move(speed) { return legs.move(speed); } walk() { return this.move('slow'); } } ``` We could even shorten these single-line-return methods to look like arrow functions. Normally this wouldn't be a big deal, but size matters since JS is commonly shipped across the network. Shorter is better. ```js class Animal(legs) { move(speed) => legs.move(speed) walk() => this.move('slow') } ``` And wouldn't it be nice if we could leave off that extra `this.`? `move` method should be available locally. ```js class Animal(legs) { move(speed) => legs.move(speed) walk() => move('slow') } ``` This syntax could de-sugar to: ```js function Animal(legs) { let move = this.move = (speed) => legs.move(speed); let walk = this.walk = () => move('slow'); } ``` Which in itself isn't too bad, except that you have to repeat the names of public reference in order to avoid using "this" in any method bodies... Private variables could be designated like so: ```js class Animal(legs) { moves = []; // ... } ``` Which would de-sugar to: ```js function Animal(legs) { let moves = []; // ... } ``` So variable assignments are always locally-scoped and private. It's not possible to reassign global variables in a class constructor, or to expose public variables... This is a little bit of me exposing my personal coding philosophy, but I find public member variables to be an anti-pattern. We have getters/setters in JS so they're not really necessary. And getters/setters can give you "read-only" variables: ```js class Animal(legs) { moves = []; get moves() => moves // ... } ``` The alternative to this would be using more keywords. `public` and `const` probably. ```js class Animal(legs) { public const moves = []; // ... } ``` This is OK, but it's limiting because you can't do any transformations on the private state before it is returned (e.g. cloning or returning an iterator instead of the array itself). It's not extensible. So once you want to do anything fancy, you'd have to move to getters/setters anyways. It's even worse for setters because it's even more likely you might want custom code to run in a setter. So it seems like `public const foo` wouldn't actually be shorthand and is more limiting than getters/setters. I think my proposal is better than the status quo because: * It's about as terse as you can possibly get. No extraneous keywords for (`this`, `function`, or even `return` in simple cases). * It still works in a way that JS devs should be familiar (function scoping) * Private variables are actually private * It's not possible to re-bind the `this` context of instance methods so code like `promise.then(animal.walk)` works as intended. Best of all, *this type of syntax could be compatible with the current ES6 class syntax*. The parens after the class identifier indicate you're using this alternate format so they can live side-by-side harmoniously. You can use one or the other syntax depending on whether you need the prototype format or the function-scoped format. This may be confusing to newbies because now there's two subtly different ways to define a class. I could also see JS engines just optimizing the terse format so it doesn't have as much memory consumption in the first place. Then the difference is less important and you just use whatever syntax you prefer. -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ ES6 classes are happening, and there's no way the syntax is going to change radically just for me this late in the game, but I still figure what the heck. Maybe I can write a language someday that has this feature. Most class syntaxes I've ever seen are implemented by specifying one specially-named method to be the constructor, e.g. __construct or constructor or {NameOfClass}. This is bad because: * It is syntactically awkward. Constructors are not methods. They shouldn't be defined like one. * They require you to manually store references to private variables. Constructors for well-designed classes just take their arguments and assign them to private variables. This mind-numbing boilerplate gets maddening quickly. -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ ES6 classes are happening, and there's no way the syntax is going to change radically just for me this late in the game, but I still figure what the heck. Maybe I can write a language someday that has this feature. Most class syntaxes I've ever seen are implemented by specifying one magically-named method to be the constructor, e.g. __construct or constructor or {NameOfClass}. This is bad because: * It is syntactically awkward. Constructors are not methods. They shouldn't be defined like one. -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 18 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,11 +4,11 @@ Most class syntaxes I've ever seen are implemented by specifying one magically-n * It is syntactically awkward. Constructors are not methods. They shouldn't be defined like one. * They require you to manually store references to private variables. Constructors for well-designed classes just take their arguments and assign them to private variables. This mind-numbing boilerplate gets maddening quickly. For example, ES6 classes will look something like this: ```js class Animal { constructor(voiceBox, legs) { this.voiceBox_ = voiceBox; this.legs_ = legs; } @@ -23,13 +23,16 @@ class Animal { } ``` You can see that for the injected services, we have to stored references on the instance. This is a little backwards because in JS `this.*` always makes the var a publicly accessible member of the class. You need an external compiler to check accesses for you, which isn't great because it isn't enforced in uncompiled code. This fact commonly leads to bad code like: * Tests that access "private" state (because it's actually public). * Subclasses that reach into private state of a super class. * A private member variable of a subclass can override that of the super class, causing bugs. * etc... Instead, if we used the in-built function scoping of ES5, the class definition would get more compelling IMHO: ```js class Animal(voiceBox, legs) { sound() { return voiceBox.makeSound(); @@ -41,27 +44,31 @@ class Animal(voiceBox: VoiceBox, legs: Legs) { } ``` We can even shorten these single-line w/ return methods to look like arrow functions. Normally this wouldn't be a big deal, but size matters since JS is commonly shipped across the network. Shorter is better. ```js class Animal(voiceBox, legs) { sound() => voiceBox.makeSound() walk() => legs.move() } ``` This is better, because: * It's about as terse as you can possibly get. No extraneous keywords (`this`, `return`, `function`). * It still works in a way that JS devs should be familiar (function scoping) * Private variables are actually private At the end of the day this syntax is sugar for: ```js function Animal(voiceBox, legs) { this.sound = () => voiceBox.makeSound() this.walk = () => legs.move() } ``` Best of all, *this syntax is compatible with the current class syntax*. The parens after the class identifier trigger the alternate format so they can live side-by-side harmoniously. You can use one or the other syntax depending on whether you need the prototype format or the function-scoped format. This may be confusing to newbies but I suspect explaining it would be as simple as "use the terse format unless you need to optimize for memory consumption". -
ewinslow revised this gist
Dec 30, 2014 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ Classes are happening, and there's no way the syntax is going to change radically just for me this late in the game, but I still figure what the heck. Maybe I can write a language someday that has this feature. Most class syntaxes I've ever seen are implemented by specifying one magically-named method to be the constructor, e.g. __construct or constructor or {NameOfClass}. This is bad because: * It is syntactically awkward. Constructors are not methods. They shouldn't be defined like one. -
ewinslow renamed this gist
Dec 30, 2014 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
ewinslow created this gist
Dec 30, 2014 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,67 @@ Classes are happening, and there's no way the syntax is going to change radically just for me this late in the game, but I still figure what the heck. Maybe I can write a compile-to-JS language someday that has this feature. Most class syntaxes I've ever seen are implemented by specifying one magically-named method to be the constructor, e.g. __construct or constructor or {NameOfClass}. This is bad because: * It is syntactically awkward. Constructors are not methods. They shouldn't be defined like one. * They require you to manually store references to private variables. Constructors for well-designed classes just take their arguments and assign them to private variables. This mind-numbing boilerplate gets maddening quickly. For example: ```js class Animal { constructor(voiceBox: VoiceBox, legs: Legs) { this.voiceBox_ = voiceBox; this.legs_ = legs; } sound() { return this.voiceBox_.makeSound() } walk() { return this.legs_.move(); } } ``` It's silly because in JS `this.*` always makes the var a publicly accessible member of the class. You need an external compiler to check it for you and even then that's not perfect because it isn't enforced in uncompiled code. This commonly leads to bad code like: * Tests that access "private" state (because it's actually public) Instead, if we used the in-built function scoping of ES5, the class definition gets even more compelling: ```js class Animal(voiceBox: VoiceBox, legs: Legs) { sound() { return voiceBox.makeSound(); } walk() { return legs.move(); } } ``` We can even shorten single-line return methods to look like arrow functions. Normally this wouldn't be a big deal, but size matters since JS is commonly shipped across the network. Shorter is better. ```js class Animal(voiceBox: VoiceBox, legs: Legs) { sound() => voiceBox.makeSound() walk() => legs.move() } ``` This is a lot terser because it doesn't require any references to "this." and still works as JS devs will be familiar, as if they had defined the class like so: ```js function Animal(voiceBox: VoiceBox, legs: Legs) { this.sound = () => voiceBox.makeSound() this.walk = () => legs.move() } ```