Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Splitting CS into Language plus Standard Library #2053

Closed
goomtrex opened this issue Jan 18, 2012 · 6 comments
Closed

Splitting CS into Language plus Standard Library #2053

goomtrex opened this issue Jan 18, 2012 · 6 comments

Comments

@goomtrex
Copy link

TL;DR

Arguing for the removal of class, extends, and (preemptively) extended, to be replaced by a standard library, possibly supplemented with other syntactic constructions:

A = extend B, C, D, ->
  @constructor: (args...) ->
    @@all.push @
  @@all = []

A = (args...) ->
  @all.push @
.all = []
::toString -> "an A"

Introduction

This is a kind of "philosophical issue" concerning the direction of CS.

It mainly concerns constructs in CS like loop, class extends, and if it gets included, the extended hook.

My main problem with the extended hook is that it introduces higher-level machinery into the language that should be provided by the program itself. In doing so, CS becomes separate to JS – in addition syntax translation, the language has baked in assumptions about its environment. This "proposal" is about enriching the language at the base level to make it easier to handle common patterns like class extends and extended outside the language.

Background

Classes in CS implement a very constrained idea of inheritance. As a purely syntactic construction, trying to implement mix-ins or other extensions gets clunky:

class A extends (mix B, C, D) # `mix` occurs before `extends`, so there's no way of injecting post-extends hooks.

class A extends Base          # `@extend` occurs after `extends`, but before `definition` (unless it's at the end), so there's no way of injecting post-definition hooks.

extend B, C, D, class A       # `extend` happens after `extends` and definition and can inject post-extends and post-definition hooks.

A = extend B, C, D, ->        # might as well just do this...
  ...

The proposed extended hook goes some way towards solving this problem...

However the extended hook seems like it's compensating for the deficiencies of class extends, of having what should be a library construct baked directly into the language. Since we can't build on extends as a function, we have to extend the language itself to get what we want.

Instead of elaborating some kind of class mechanism in CS, I think the emphasis should be on the lower level elements that can support this kind of behaviour in a library.

What does the current system provide?

Named functions:

class A extends Drugs
# =>
function A () {}

Class extension:

function extend( child, parent ) {
  // ...
  function ctor() { this.constructor = child; }
  ctor.prototype = parent.prototype;
  child.prototype = new ctor;
  child.__super__ = parent.prototype;
  return child;
}

Clean definition syntax:

class A
  constructor: ->
    @all.push @
  @all = []
  toString: -> "an A"

Executable class bodies:

class Logger # *yoink*
  if env is 'development'
    log: (err) -> console.log err
  else if env is 'production'
    log: (err) -> console.error err

And most importantly, super:

class A
  toString: -> "an A"

class B extends A
  toString: -> "a B (was #{ super })"

How can this be implemented as a library?

Named functions:

Reintroducing named functions is possible, but controversial, but essentially non-essential.

Class extension:

The extends functionality is already external to the language.

Clean definition syntax:

The current syntax is certainly neat, but nothing that couldn't be arrived at in another way:

class A
  @classVar = []
  @::protoVar = ->

Executable class bodies:

As extends can be written as a function, we can just use a function for our definition:

A = extend B, ->
  @classVar = []
  @::protoVar = ->

Using objects as definitions:

Using object literals instead of objects can make things a bit cleaner:

A = extend B
  @classVar = []
  protoVar: ->

It just means out prototype variables have to be last.

The super reference:

This is the most difficult part, since it's hard to figure out if a given function is class-bound, in which case we want this.__super__, or if it's prototype bound, in which case we want this.constructor.__super__.

Short of specifying exactly which one, we could just use the @:: syntax above to distinguish prototype and class functions.

What features could be added to support class-extends?

Executable object literals

If object literals themselves were translated from:

a: 1, b: 2

To:

new function () {
  this.a = 1;
  this.b = 2;
}

Then we'd have a bit more flexibility in how we define our class:

(Here, extend executes the definition on the prototype.)

A = extend B, ->
  protoVar1: ->
  @@classVar = []
  protoVar2: ->

Or even:

A = extend B, ->
  @constructor: ->
    @@all.push @
  @@all = []

This also presumes the existence of a @@ operator meaning this.constructor.

Mustache

If the chaining vs cascading syntax gets nailed, we wouldn't even need abstraction:

Thing = ->
  @createdAt = new Date()
::toString -> "I am a thing."

Would compile to:

Thing = function () {
  @createdAt = new Date()
}
Thing.prototype.toString = function () { return "I am a thing." }

Loop

If there's an argument for extracting language features into functions, I think loop definitely qualifies:

loop
  forever

Gets translated to:

while true
  forever

It's another keyword for what is a dubious control structure in the first place...
It can be extracted into a function quite easily:

loop = (f) -> f() while true

Or for the asynchronous among you:

loop = (f) -> setTimeout arguments.callee, f()

Which really underlines the argument of having these kinds of things as library functions rather than rigid language-bound constructs.

@showell
Copy link

showell commented Jan 18, 2012

A small note. The "loop" construct already exists; it's just kind of buried in the docs. See #2039 (comment) for suggestions on improving the docs.

@showell
Copy link

showell commented Jan 18, 2012

@alexkg This is an interesting proposal, but I think there are at least two show stoppers.

First, I think many folks like the current "class" syntax, as it allows you to express object factories with a syntax that is very nice and familiar to Ruby/Python programmers. Having said that, and despite having a lot of experience in Python/Ruby myself, I tend to avoid "class" in CoffeeScript, as I prefer the function closure style where a function returns a new object with functions that act on closed variables. So, I am certainly intrigued by your proposal, just doubtful it will fly with others.

I'm not sure how integral this feature is to your proposal, but I really don't want objects to be wrapped:

new function () {
  this.a = 1;
  this.b = 2;
}

I'm -1 on the proposal as it stands, but I hope it leads to some thoughtful discussion. I think I understand the gist of what you're after. Basically, you're saying that a slightly more lean syntax that's focused on objects and functions can ultimately be more flexible/extendible in terms of creating objects, without prejudicing you toward a standard notion of "class" as a way to construct the objects, correct? Or is it something else you're after?

@goomtrex
Copy link
Author

Hi @showell thanks for your comments.

Your understanding is correct and you're right that it won't suit everyone. Je suis un minimaliste, I suppose. I guess the question is how much the core language be improved (in terms of operators, syntax, etc) to build these kinds of syntactic abstractions in a DSL kind of way.

The object to new function translation is a bit more of a tangent, just something I have been thinking about for nested objects:

lookUp:
  verticalDirections: [ "up",  "down" ]
  horizontalDirections: [ "left", "right" ]
  directions: verticalDirections.concat horizontalDirections

And as above, it might help clean up a library based class syntax.

BTW I would actually like to get rid of loop, sorry that wasn't really clear...

@devongovett
Copy link

Nope. It is impossible to do classes right as a library, and we've seen this countless times in the JavaScript world. That is why this is a language feature, and I believe it should continue to be. We've also written tons of code on CoffeeScript classes by this point, so simply removing them and replacing with a half-baked library is not a desirable option on all fronts.

@goomtrex
Copy link
Author

goomtrex commented Feb 3, 2012

It is impossible to do classes right as a library, and we've seen this countless times in the JavaScript world.

I guess the question can be rephrased as: what's missing from JS that makes writing classes "impossible"?

Why not focus on these issues, making CS more versatile at a base level rather than providing higher level syntactic abstractions?

I think some of the things like monocle mustache and @@ can fill that gap, and a lot more...

@GeoffreyBooth
Copy link
Collaborator

I hate closing such a thoroughly written issue, but with CS2 now outputting ES class and extends, the goal of moving this logic out of CoffeeScript has been achieved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants