Closed jussiry closed 12 years ago
Interesting idea.
I don't think it'll make it in though.
I like this idea. I use the do
keyword quite a bit, and foo.bar!
is more idiomatic than do foo.bar
for methods which alter the object.
Thanks for the feedback, i'd really love to see this feature implemented. Though i don't see how do
is related, since do foo.bar
only compiles to foo.bar();
Outside of string operations , I'm not sure that foo = foo.bar()
is idiomatic enough in JS to deserve dedicated syntax. Strings are among the only immutable objects in the language, and I'm not familiar with common APIs where instance methods routinely return a modified version of themselves without also mutating the receiver. (Array slices come to mind, but we already have dedicated syntax for these.) In Ruby, the exclamation point is actually part of the method name, whereas under this proposed syntax, every method could be called both with and without the exclamation point; and in most cases, the presence or absence of this syntax would not help a reader to understand the purpose of the code. All existing mutative functions would work as written without the !
mark, and code like node.querySelector! "div"
would be possible, even though it represents a search rather than a destructive change.
The proposed syntax would actually be a form of compound assignment, akin to +=
, *=
etc. I think .=
may have already been suggested for this purpose, and while I much prefer the look of this proposed syntax, I feel that it suffers by not looking enough like an assignment. In this form, is it expressive or useful enough to be a part of the language?
See #447 and #1335. I like this syntax more than those though, even though it's not as obvious at first what it does.
-1
In Ruby there is a big difference between foo.bar!("yo") and foo = foo.bar("yo"). It's like the difference between modifying source code and forking it. In Ruby "bang methods" are hand coded to actually mutate the original object, so that all references to that object benefit from the side effect. In this proposal CS would be using the former syntax to implement the latter semantics, which is just gonna lead to confusion.
Also, there is a strong convention in JS to use non-banged names for mutator methods. Consider Array. The methods pop, push, reverse, shift, sort, splice, and unshift all mutate Arrays without having a bang at the end.
I know Ruby has '!' only as part of method name, but personally i'v come across situations where i wasn't sure if i should modify the original object or just return the modified version in prototype methods i'v written. And writing both modifying and non-modifying version of the methods feels like bit of a waste, so for me it would be an improvement if the user of that method could decide each time if they want to modify the original object or not.
Sorry, didn't mean to close this.
@jussiry wrote: "And writing both modifying and non-modifying version of the methods feels like bit of a waste, so for me it would be an improvement if the user of that method could decide each time if they want to modify the original object or not."
You have to write two versions of the method. You can't magically have a non-modifying version of a method suddenly modify the original object. Reassigning a variable is not the same as modifying the original object.
There is an enormous difference between @jussiry's idea and Ruby's implementation of !
in method names. It's simply a convention in Ruby to mark "dangerous" methods with a !
. It doesn't actually do anything -- it's just an indicator -- leaving it up to the developer to make the method actually modify the object.
In your example, foo.bar!
would compile to foo = foo.bar();
, binding the value of foo
to the return of foo.bar
with the assumption that foo.bar
returns foo
(implied in your examples with chaining).
If the !
'ed method does not return the object it was called on then you will likely run into issues.
foo =
bar: (x)-> x * x
# foo.bar!(5)
foo = foo.bar(5)
console.log foo # 25
foo
is no longer an object, but is now a number. So:
# foo.bar!(5).bar!(5)
(foo = foo.bar(5)).bar(5)
console.log foo # TypeError: Object 25 has no method 'bar'
@caseyohara Just to be clear, are you -1 on this idea now? The intentions behind this proposal are fair enough, but the proposal is flawed, and the issue should be closed.
# foo.bar!(5).bar!(5)
You wouldn't be able to chain like that (as far as I understand the proposal).
(foo = foo.bar(5)) = (foo = foo.bar(5)).bar(5)
doesn't make sense.
I agree. I'm in favor of the idea if the implications of the proposed compilation are clear.
For instance, what are the ramifications of !
ing a regular function?
square = (x) -> x * x
square!(5)
# compiles to
# square = square(5);
console.log square # 25
The variable square
which was bound to a function is now a number. I'm not sure there's a practical use for that.
Ruby's !
is a convenient naming convention and it's too bad it's not allowed in JS identifiers. But Coffee shouldn't be a port of all of Ruby's conveniences. That said, I think this is an opportunity for Coffee to improve on Ruby's !
by making it a real feature instead of simply a convention.
Trying to find a use case for ! is sort of like having a hammer in search of a nail. It's difficult to make ! a real feature, because there's no magical way to turn a non-mutating method into a mutating method. There's a reason that ! is only a convention in Ruby, and not a feature.
Having said that...
You can actually create ! methods in CS, but they're missing syntax sugar. I suppose CS could provide sugar for this code:
counter = (n) ->
self = {}
self['incr!'] = -> n += 1
self.incr = -> counter(n + 1)
self.n = -> n
self
c1 = counter(1)
console.log c1.n() # 1
c1['incr!']()
console.log c1.n() # 2
c2 = c1.incr()
console.log c2.n() # 3
c1['incr!']()
c1['incr!']()
c1['incr!']()
# c2 is still 3
console.log c2.n() # 3
With sugar, you'd write this:
counter = (n) ->
self = {}
self.incr! = -> n += 1
self.incr = -> counter(n + 1)
self.n = -> n
self
c1 = counter(1)
console.log c1.n() # 1
c1.incr!()
console.log c1.n() # 2
c2 = c1.incr()
console.log c2.n() # 3
c1.incr!()
c1.incr!()
c1.incr!()
# c2 is still 3
console.log c2.n() # 3
I'm not sure CS really needs this, but it's food for thought.
-1
Though Scheme conventions for non-consing methods and predicate methods would be interesting. Too bad ?
is already taken =/
Also, there is a strong convention in JS to use non-banged names for mutator methods. Consider Array. The methods pop, push, reverse, shift, sort, splice, and unshift all mutate Arrays without having a bang at the end.
Well... JS doesn't support special characters in an identifier, unicode aside -- though that differs from implementation to implementation. The thing is that ! is used to add distinction between a destructive and a non-destructive version of a functionality, thus it little sense if you only have a destructive version. Imho.
Seems like i didn't quite think this through; reassigning a variable instead of modifying the original object is a big defect. So i started thinking, what if we create a helper function to change the original object? Something like this:
__changeAtoB = (a,b)->
throw "Error!" if a.constructor != b.constructor
delete a[key] for own key,value of a
a[key] = value for own key,value of b
a
And thus foo.bar!
would compile to __changeAtoB(foo,foo.bar())
Well, turns out this would work for almost all objects, except for Strings, Numbers and Booleans, since, as noted by @sethaurus, they are immutable. So at this point i think i'll give up. Thanks for the discussion!
ps. I would still +1 @showell's idea on the syntactic sugar to allow ! in function names.
@jussiry Allowing ! in identifiers would be problematic at top level scope. Otherwise, I would be more in favor of it. Thanks for both opening and closing the issue--it was definitely worthy of discussion, even if it was ultimately flawed.
In Ruby exclamation marks in function names are used to indicate that the function changes the object in question (for example: "foo".capitalize!). CoffeeScript could do something even cooler, and make this functionality dynamic, so that:
foo.bar!
would compile tofoo = foo.bar();
This would allow for design pattern where prototype functions by default would never need to alter the object, but this could be chosen when the function is called simply by adding (or by not adding) exclamation mark.
Here's another, bit more complex example:
would compile to