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

Constructor Arguments enhancements (4) #92

Closed
josher19 opened this issue Jan 15, 2010 · 13 comments
Closed

Constructor Arguments enhancements (4) #92

josher19 opened this issue Jan 15, 2010 · 13 comments

Comments

@josher19
Copy link
Contributor

I have some suggestions for making CoffeeScript Classes even easier to use
in a friendly, "Unfancy" way.

  • First, is to have an easy way to give the default value.
  • Second, is a quicker way set my class variables:
    this.var_name = var_name;
  • Third, is a "mustbe" clause which ideally would be checked at compile time instead of while running and a "force" clause to coerce an argument to of a given Type or Class.
  • Fourth, some examples of how these changes will make the code more "Unfancy", readable, and less buggy.

default ??

1.) Allow a "default" in function args or ?? in expressions as a shortcut.
Existence (?) is great, but it can be even better if we can use it like the || operator.

speed default 45
# is equivalent to any of:
speed ??= 45
speed: speed ?? 45
# current syntax:
speed: if speed? then speed else 45

// in javascript, all 4 would become:

   speed = (typeof(speed) !== 'undefined' && speed !== null) ? speed : 45;

which is much smarter way to do it than

speed = speed || 45;
Bug-prone CoffeeScript example:
Horse: name, speed => 
    this.name: name
    this.speed: speed || 45

later: =>
    deadHorse = new Horse("Gluey", 0);
    "This horse has a speed of " + deadHorse.speed  + "!"
     # ... 45!

See http://gist.github.com/274158
for more examples of common JavaScript class parameter shortcut bugs.

Better CoffeeScript example:
Horse: name, speed => 
    this.name: name
    this.speed: if speed? speed else 45
Best, "Unfancy" CoffeeScript example:
Horse: name, speed default 45 => 
    this.name: name
    this.speed: speed

my

  1. A quicker way set my class variables would be using "my":
    my: => this.attr: attr
Horse: name, speed => 
    this.name: name
    this.speed: if speed? speed else 45

becomes a one liner:

Horse: my name, my speed default 45 => 

It would be nice to have in the compiler so it works well with default:

MyClass: my _x default _y => 

    # is more or less equivalent to:
    MyClass: _x => this._x : _x : _x ?? _y
    # _y should not show up in MyClass.arguments 
    # since it is usually a constant default value.

// in javascript becomes:
function MyClass(_x) {
   this["_x"] = _x = (typeof(_x) !== 'undefined' && _x != null) ? _x : _y;
}

// for Horse example: Horse: my name, my speed default 45 =>

function Horse(name, speed) {
     this.name = name;
     this.speed = speed = (typeof(speed) !== 'undefined' && speed !== null) ? speed : 45;
}

mustbe, force

  1. One thing that gets newbies and sometimes veteran Javascript programmers is giving the wrong type of argument to a function or constructor. A common example is forgetting to convert user input in a textfield from a String ("42") to a Number (42).
    A "mustbe" (throw Error) or "force" (try to convert to new Class) clause, which ideally would be checked at compile time (against primitives, anyway) instead of while running, would be great!
# CoffeeScript:
Horse: my name mustbe "string", my speed default 45 force Number => 

// javascript:
function Horse(name, speed) {
   // name mustbe String
   if (! isType(name, "string") ) throw new ArgTypeError(name, "string");
   // my name
   this.name = name;
   // speed default 45
   speed =  (typeof(speed) !== 'undefined' && speed != null) ? speed : 45;
   // speed force Number
   if (! isType(speed, Number) ) speed = new Number(speed) ;
   // my speed
   this.speed = speed;
}

Sample Javascript and tests for ArgTypeError and isType are in this Gist:
http://gist.github.com/277078

Example Usage

  1. Extended CoffeeScript examples using proposed new syntax additions:
    // See: http://gist.github.com/gists/274170
Animal: =>
Animal::move: meters =>
  alert(this.name + " moved " + meters + "m.")
Animal::toString => this.name || this.constructor || "ANIMAL";

Veg: =>
Veg::toString => "Veggies"

Mineral: =>

Snake: my name =>
Snake extends Animal
Snake::move: =>
  alert("Slithering...")
  super(5)
Snake::eats: prey mustbe Animal =>
  if prey.speed + 1 > this.speed then 
      alert(prey + " escapes!") 
  else 
      alert([prey, "swallowed by",this.name].join(" "));

Horse: my name, my speed default 45 force Number => 
Horse extends Animal
Horse::move: =>
  alert("Galloping...")
  super(this.speed)
Horse::eats: prey mustbe Veg => alert("chomp chomp");

sam: new Snake("Sammy the Python")
tom: new Horse("Tommy the Palomino")

// sam.move()
// tom.move()

babyspeed: `$ && $('#speed').val();` # grab from user input, forget to convert to Number.

foal: new Horse("Baby Foal", babyspeed || "4")
grass: new Veg()

alert(getType(foal.speed)) # Number

tom.eats(grass)  # chomp
sam.eats(tom)    # tom escapes
sam.eats(foal)   # gulp!

# won't work:
sam.eats(grass)  # error!
tom.eats(sam)    # error!

Note: without the "force Number" clause, the foal unexpectedly escapes:

"4" + 1 > 5
"41" > 5
true

instead of

4 + 1 > 5
5 > 5
false
@jashkenas
Copy link
Owner

First off, you win the prize for prettiest issue of the week:

Trophy

I'll need more time to give you a more than superficial response, but at first glance:

  • Conditional assignment based on existence sounds like a great idea. We should definitely do it.
  • Re: "my" and "default", I'm not such a big fan of cluttering up the parameter list with defaults and assignments -- that logic seems better suited to the body of the function.
    • I'd rather not get into type checking as a feature. It's both counter to the spirit of JavaScript-esque languages, something that JS has very poor support for (look at the isType family of functions in Underscore for an example of how sketchy the implementation can be), and also opens the trapdoor to a huge pit of static type checking that we'd be better off skirting around.

All that aside, your suggestions go together hand in hand, and would make for an interesting branch of CoffeeScript, if you want to take a stab at implementing it. I'm sure there are plenty of interesting avenues you could take, if you decided to allow predefined helper functions and compile-time type checking.

Thanks for the lovely ticket.

@weepy
Copy link

weepy commented Jan 15, 2010

I like the idea for a ?? and ??= operator, since we already have ? and it's a common anti-pattern.

@zmthy
Copy link
Collaborator

zmthy commented Jan 15, 2010

I agree about the type-checking, it's not really in the spirit of the language. However, as pointed out in the issue, sometimes it is important an argument be of a specific type, and it's a pain having to run conversions on each one. Having a quick way to ensure the arguments are correct at runtime sure would be handy.

@weepy
Copy link

weepy commented Jan 15, 2010

it would be neat if the pattern matching could work with the ||= operator. I.e. you could set defaults for inbound arguments with one line:

[a,b,c] ||= [x,y,z]

compiling to

var __a, a, b, c;
__a = [x, y, z];
 a = a ? a : __a[0];
 b = b ? b : __a[1];
 c = c ? c : __a[2];

@jashkenas
Copy link
Owner

Getting all flavors of assignment to work with pattern matching is going to be a bit of a trick, but conditional assignment based on existence is now on master.

a ?= 5

Compiles to:

var a;
a = (a !== undefined && a !== null) ? a : 5;

Note that we don't have to do the string comparison on typeof === 'undefined' for the existential assignment, because if it's not in scope we declare it at the top.

...

And finally, you can now use the existential operator infix as well:

result: attempt ? give_up 

@jashkenas
Copy link
Owner

As for syntax that's a shortcut for this., what do y'all think about this:

Person: first, last =>
  :name: first + last

Person::introduce: =>
  print("Hello there, I'm " + :name)

@weepy
Copy link

weepy commented Jan 17, 2010

Ah - nicely done with all these jash.
I like how the :x mirrors the prototype ::

@zmthy
Copy link
Collaborator

zmthy commented Jan 17, 2010

I'm unsure about the ambiguity of saving a property on the context.
value: this.prop
could exist as
value: :prop
which is very close to
value::prop
which could technically mean either of those statements, right?

@jashkenas
Copy link
Owner

Yeah, I think it's too fuzzy as well -- overloads the colon to the breaking point. Closing the ticket...

@weepy
Copy link

weepy commented Jan 17, 2010

Using @ wouldn't be fuzzy as it's not used for anything else :-D.

@josher19
Copy link
Contributor Author

@jashkenas:

First, Thanks for the Award!
I cut & pasted the text from a Gist and it was a real mess until I put it in the Markdown Previewer and fixed it up. From now on, I'm writing my text there first before posting to preview how it looks.

In theory, you could optimize

a ?= 5

To compile to:

var a;
if (typeof a !== 'undefined' && a !== null) a = 5;

or

var a;
if (a != null) a = 5;

and save the unnecessary assignment of:

a = a;

when a is undefined. Last time I checked (a long time ago) IE 6 died on a === undefined , but null == undefined on all browsers (that's == not ===).


I like the idea of the new infix existential operator:

result: attempt ? give_up

But it may look too close to ternary op ?: in Javascript, which is why I suggested ??

I can see JavaScript coders mis-reading the code as:

 result = attempt ? give_up : null;

instead of

 result = (typeof attempt  !== 'undefined' && attempt  !== null) ? attempt : give_up;

@josher19
Copy link
Contributor Author

@Tesco
Although it may not be in the spirit of the language, sometimes type checking is needed, and that can be a real pain because of some of JavaScript's quirks. Example: instanceof does inhertitance

var tom = new Horse(); tom instanceof Object === true;

but does not do primitive types:

  var str="This is a string"; str instanceof String

is false even though the constructor is true:

var str="This is a string"; str.constructor === String

So something to ease the pain of doing type checking would be great! Maybe I should just make isType into an (optional) external CS or JS library?

@weepy:
I agree, an @ would be nice:

Person: first, last =>
  @name: [first, last].join(" ")

Person::introduce: =>
  print("Hello there, I'm " + @name)

@jashkenas
Copy link
Owner

@josher:

Underscore.js already has a pretty nice isType suite of checks, optimized for speed. If you'd like to contribute to those, I'd be glad to take the patches. They are:

isEqual, isEmpty, isElement, isArray, isArguments, isFunction, isString, isNumber, isDate, isRegExp isNaN, isNull, isUndefined

http://documentcloud.github.com/underscore/

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

No branches or pull requests

4 participants