Skip to content
This repository has been archived by the owner on Feb 19, 2018. It is now read-only.

CS2 Discussion: Question: New Feature Ideas #16

Closed
rattrayalex opened this issue Jul 28, 2016 · 21 comments
Closed

CS2 Discussion: Question: New Feature Ideas #16

rattrayalex opened this issue Jul 28, 2016 · 21 comments

Comments

@rattrayalex
Copy link
Contributor

What new features would you love to see added to CoffeeScript?

Let's list them here, ideally one-idea-per-post, and then open new threads for features that garner interest.

@carlsmith
Copy link

carlsmith commented Jul 28, 2016

Extensibility

Some combination of macros, compiler options and AST transformers would allow users to define their own dialects without having to create a fork. We have a community of language designers, and have let that become a force that fractures our work into tons of abandoned projects, each adding one or two features in isolation. Having a community of hardcore, programming language enthusiasts should be a positive thing.

Using a parser generator will make our compiler more difficult to extend. A handwritten parser can be extended more easily. A dialect should be distributable as a compiler configuration, which would typically be a few KB of JSON.

A lot of the new features people want are parsed identically to existing features. Prefix, infix and suffix operators are more or less generic. You just need to define the spelling, the precedence and what it compiles to. We can also generalise constructs like keyword-predicate-block, where again, you only need define a spelling for the keyword, the precedence of the expression as a whole, and what it compiles to. The predicate is a generic expression, so that will parse automatically, and the same is true for any block, so it's actually just a matter of defining what the header compiles to.

We could probably just map new operators and other constructs to invocations, passing any operands, predicates and blocks along as lambdas, so the user only needs to provide a spelling, a precedence and a runtime function that can handle the call.

I've explored actually implementing this, and it isn't that complicated. The only issue is wanting to change so many other things in the process, you end up with a totally new language :\

@carlsmith
Copy link

carlsmith commented Jul 28, 2016

Pythonic For

I explained the logic behind this at some length in #11, but to summarise again here: If we allowed unpacking a sequence inside a for-loop when the names we're assigning are wrapped in brackets (for [key, value] in hash.items()), then we would have the option of using Python style for-loops without breaking backwards compatibility, assuming we want to keep something like the old behaviour as well. The square brackets are perfectly consistent with CoffeeScript assignment syntax:

[a, b, c] = [1, 2, 3]

@carlsmith
Copy link

carlsmith commented Jul 28, 2016

Named Functions

The grammars <name> = <function literal> and <expression>.<name> = <function literal>should set the name property of the function to <name>. This was implemented, but it broke IE6, and was removed. It has been discussed in depth in CoffeeScript Issue 15, as well as many other issues.

There is a proposal to add the same logic to ES6, so this feature may just magically start working in CoffeeScript soon!

@carlsmith
Copy link

carlsmith commented Jul 28, 2016

Object and Set Comprehensions

We don't have these, and they're awesome. It's one of my favourite Python features that CoffeeScript lacks.

@carlsmith
Copy link

Proper Equality Operator

An operator that tests the equality of arrays, objects and sets, based on their contents, like every other scripting language has, so expressions like [] equals [] and {a: 1} equals {a: 1}would be true.

@carlsmith
Copy link

carlsmith commented Jul 28, 2016

Falsey Empty Collections

Empty arrays, objects and sets should be falsey when used as a predicate.

@carlsmith
Copy link

carlsmith commented Jul 28, 2016

Operator Overloading

It's a really useful feature, especially for library authors.

@carlsmith
Copy link

Automatically Reference First Args

The first argument to any function should be assigned to a reserved name. Other dialects have used it for this.

employees.forEach -> console.log it.name, "makes", it.salary

I'd rather assign the arguments array to args and make it a regular array, then assign each arg to arg0, arg1 and so on. Obviously, we'd only do this for those names that are actually referenced in the code, but it would make a lot of callbacks more succinct.

@kirly-af
Copy link
Contributor

kirly-af commented Aug 3, 2016

Pipe Operator

There's a PR on the CS repo.
I would love to use it. However I'm not sure I should create a separated issue on this repo.

@objectkit
Copy link

objectkit commented Aug 4, 2016

Method Overloading

Would be great if method overloading was introduced, at least in the context of classes.

class LoginService extends HttpService

    login: (username, password) ->
        @login new Credentials username, password

    login: (credentials) ->
        @post @loginUrl, credentials.toJSON()

Just hypothetical code here - but in this scenario, by providing a username and password to one method, a Credentials object could be created before being passed of to another implementation of the method. The Credentials object could be used in multiple ways - converting itself to JSON, validating username or password strings on instantiation... There are better use case scenarios.

In terms of code output, the above classes login method could end up looking something like below in ES 4/5 pseudo code:

LoginService.prototype.login = (function () {
    var funcMap = {
        "1": function (a) {
           return this.post(this.url, a);
        },
        "2": function (a, b) {
            return this.login(new Credentials(a, b));
        }
    }
    return function () {
        var func = funcMap[arguments.length];
        if (null == func) {
            throw new Error("no such method message");
        }
        return func.apply(this, arguments);
    }
}).apply(this);

There are better ways to do this, but there will be overhead from method lookup, but acceptable I think. In production code with a fixed strict spec, it would be possible to eliminate the redundancy of method overloading if the need wasn't presented, resulting in code with more direct access to functions without lookup required...

No idea if this would work for standalone functions, but don't see why not...

@objectkit
Copy link

objectkit commented Aug 4, 2016

Enum

enum State
    Pending
    Complete
    Fault

That would be somewhat similar to this in CS today:

class State

    extensible = yes

    @Pending = new class Pending extends State
    @Complete = new class Complete extends State
    @Fault = new class Fault extends State

    extensible = no

    constructor: ->
        if not extensible
            throw new Error
        @name = @constructor.name

    toString: -> @name

Of course, hashes can be used instead

State =
    Pending: "Pending"
    Complete: "Complete"
    Fault: "Fault"

or

State =
    Pending: name: "Pending"
    Complete: name: "Complete"
    Fault: name: "Fault"

but in ES today, hashes involve implicit or explicit string comparisons.

# e.g. this impl could accept either a string or object reference as an argument
fun = (state) ->
    switch state
        when State.Pending then @handlePendingState()
        when State.Complete then @handleCompleteState()
        when State.Fault then @handleFaultState()

and this is fine most of the time, but enums can also have methods, and are unique instance specific identifiers which cannot be reproduced anywhere else in code if not exposed.

The example 'fun' implementation above would handle hashes or enums due to === checks used by switch statements, but enums would also allow for instanceof checks when appropriate, whereas string pollution (e.g. using strings instead of hashes) can scatter code in may cases.

I haven't made a very strong case for enums here, but I do think they would be very useful in CS!

@objectkit
Copy link

objectkit commented Aug 4, 2016

Adopts

We can't really do interfaces in ES/CS. but we can do mixins. How about this?

class EventDispatcher

    constructor: (@target = @) ->
        @listenerMap = {}

    addEventListener: (type, handler, rest...) ->
        if not list = @listenerMap[type]
            list = @listenerMap[type] = [handler]
        else
            list.push handler

    removeEventListener: (type, handler, rest...) ->
        # impl here

    dispatchEvent: (event) ->
        # impl here

That defines an EventListener. Some classes may extend from it. Others may just need to borrow its abilities by using it as a mixin.

If we have a class that extends from another, there is no other choice but to use a mixin to aggregate its behaviour additionally, but if we introduced an 'adopts' keyword instead of 'implements', we could mixin multiple classes as well

class Child extends Parent adopts EventListener, AnotherMixin

The reason I'm suggesting an alternative to the word 'implements' is that interfaces are generally type level commitments and their implementations rarely include executable code (though that depends on the language you're using, and there are shifts there too), but the emulation of interfaces is not really where CS is!

An alternative would be to reimplement CS extends, such that the first class passed to extends must be a class, but subsequent additions will be regarded as mixins

class Child extends Parent, EventListener, AnotherMixin

The key problems are

  • some mixins will require instantiation logic (e.g. EventDispatcher), so may have to be called in the constructor of the adopting class.

    class Child extends Parent adopts EventListener, AnotherMixin
    
        constructor: ->
            super
            EventListener.apply @
            AnotherMixin.apply @, "constructor arg"
    

    or an implicit ordering of calls to 'super' (assuming super.METHOD will be implemented, and the word super is reserved entirely for constructors)

    constructor: ->
        # superclass constructor
        super 
        # EventDispatcher constructor
        super 
        # AnotherMixin constructor
        super "constructor arg" 
    

    or a nasty rethink of super in context of constructors and introduce enumeration of super constructors, deferring to adopted classes for enumeration.... explicit ordering, meaning that calls to 'interface' constructors can be called in any order if there is a need...

    constructor: ->
        # superclass constructor
        super
        # AnotherMixin constructor
        super.2
        # EventDispatcher constructor
        super.1
    

    But I could live with that and generally implement mixins that avoid using instantiation logic in their own constructors.

  • method and property name collision. It could be the case that if there is method or property name collision, that the first mixin wins - e.g. if mixin A has a method called 'action', and mixin 'B' also has a method called 'action', then a compiler error will be thrown, or, the ordering of mixins matters as each mixin will overwrite any other mixin method already applied. If a class also implemented a method called 'action', the special case would be that the class always wins, no matter what - the 'action' methods of A and B would be overwritten... ?

    class Child extends Parent adopts EventListener
        # @override
        dispatchEvent: (event) ->
            @log event
            EventDispatcher::dispatchEvent.apply @, [event]
    

    or

    class Child extends Parent uses EventListener
        # @override
        dispatchEvent: (event) ->
            @log event
            super.dispatchEvent event # reappropriate super??
    

From my POV, the word choices are 'implements', 'adopts', 'adapts' or 'uses', most likely favouring 'uses' as its declarative in relation to the implementing class, not its inheritance chain. Its not a straightforward thing to define, but I think if we put some thought into it, we could come up with a viable solution. Anyone else?

@objectkit
Copy link

objectkit commented Aug 4, 2016

Tuples

tuple = ("a", "b", "c", "d")
tuple[0] # "a"
tuple[-1] # "d"
tuple[1:3] # ["b", "c"]

Just for example. Not proposing syntax. However, AFAIK, tuple like behaviour can be implemented in ES using Arrays and index transformations, so as a target language, ES can support them once CS transpiles.

var tuple = ["a", "b", "c", "d"];
tuple[0]; // "a"
tuple[tuple.length - tuple.length - 1]; // "b" - inaccurate, but just to demo
tuple.slice(1, 3); // ["b", "c"]

I believe destructuring assignments can emulate, but I have to admit, I've failed to see how in exactly the same way? I'm sure tuple emulation must be possible in CS as is... would like some feedback on that!

@kirly-af
Copy link
Contributor

kirly-af commented Aug 6, 2016

NaN is NaN

foo is NaN and bar isnt NaN could respectively compile to isNaN(foo) and !isNaN(bar).

This way NaN is NaN would be true.

@kirly-af
Copy link
Contributor

kirly-af commented Aug 6, 2016

Generalized Loops

I talk about it in #11, but I would like to see generalized loops in CS that would handle Objects, Arrays, Strings and ES6 Iterables the exact same way.

For backward compatibility, we could introduce a foreach keyword (maybe for each or for-each).

The syntax would look like:
foreach item in someCollection or foreach index, item in someCollection.

Examples:

(console.log key, val) foreach key, val in {'foo': 'bar'}
(console.log idx, val) foreach idx, val in ['foo', 'bar']
(console.log idx, char) foreach idx, char in 'foobar'
(console.log count, item) foreach count, item in someIterrableObject

@rattrayalex
Copy link
Contributor Author

Closing this thread as we have decided not to add any non-EcmaScript features.

@rattrayalex
Copy link
Contributor Author

Though, I will point out that @carlsmith 's shalt project may be interesting to those wishing to experiment.

@rattrayalex
Copy link
Contributor Author

... and should also mention that I am beginning work on a new language called LightScript, which may incorporate some of the suggestions on this thread (and from this repo generally).

@mrmowgli
Copy link

I know his is closed but I realized how awesome it would be to have a transpiler to other languages besides Javascript, including Java byte code and self contained binaries ;) But that's just me loving coffeescript so much. Give a man a hammer and everything starts looking like a nail...

@GeoffreyBooth
Copy link
Collaborator

A few years ago I had to do some coding in PHP, and I stumbled across https://github.com/btwael/mammouth:

 __  __                                       _   _     
|  \/  |                                     | | | |    
| \  / | __ _ _ __ ___  _ __ ___   ___  _   _| |_| |__  
| |\/| |/ _` | '_ ` _ \| '_ ` _ \ / _ \| | | | __| '_ \ 
| |  | | (_| | | | | | | | | | | | (_) | |_| | |_| | | |
|_|  |_|\__,_|_| |_| |_|_| |_| |_|\___/ \__,_|\__|_| |_|

Mammouth is a small language that compiles into PHP, inspired by CoffeeScript. It's compiled to PHP codes/files that you can run in your PHP server.

If only all languages had such things 😄

@coffeescriptbot
Copy link
Collaborator

This issue was moved to jashkenas/coffeescript#4913

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

No branches or pull requests

7 participants