Help wanted: feature design: module support #51

Closed
erik-kallen opened this Issue Sep 17, 2012 · 18 comments

Comments

Projects
None yet
3 participants
@erik-kallen
Contributor

erik-kallen commented Sep 17, 2012

I want to add support for require to the compiler. Basically, it should be possible to have a class like this (given to-be-defined attributes):

public class Http {
    public static void CreateServer(Action<Connection> handler) {
    }
}

with an invocation of which should be possible to compile to something like the following:

var $http = require('http');
... other code ...
var server = $http.createServer(function(c) {
    ... code in the delegate ...
});

This should be some kind of attribute on the assembly (or the class?). This attribute should also cause the generated code to be usable as a Node module.

Additionally, addition of this attribute (with some additional flags) should allow AMD-style code to be generated, so a generated script could look something like:

define(['mscorlib', 'lib1', 'lib2'], function($mscorlib, $lib1, $lib2) {
    ... module code goes here ...
});

Unfortunately I feel like I know too little about how these things (Node/require/AMD) are used in practice, so I would like the help of the community to design the feature. If you have any idea about which attributes you'd want for this, and how the generated code should look, please share your thoughts here.

@txdv

This comment has been minimized.

Show comment Hide comment
@txdv

txdv Sep 17, 2012

Meanwhile a nice article about AMD: http://addyosmani.com/writing-modular-js/

txdv commented Sep 17, 2012

Meanwhile a nice article about AMD: http://addyosmani.com/writing-modular-js/

@dested

This comment has been minimized.

Show comment Hide comment
@dested

dested Sep 19, 2012

Im not sure if it makes sense to have createserver be a static method on HTTP. I feel like it should be an instance method on it, with the instantiation of Http emit the "require" code. (with the proper decoration)

Im afraid I cant give a lot of input on amd, but there would have to be some sort of class dependencies or something to that effect so you can properly pass them into the module. Functionally what would be the benefit to emitting the class as an amd module?

dested commented Sep 19, 2012

Im not sure if it makes sense to have createserver be a static method on HTTP. I feel like it should be an instance method on it, with the instantiation of Http emit the "require" code. (with the proper decoration)

Im afraid I cant give a lot of input on amd, but there would have to be some sort of class dependencies or something to that effect so you can properly pass them into the module. Functionally what would be the benefit to emitting the class as an amd module?

@erik-kallen

This comment has been minimized.

Show comment Hide comment
@txdv

This comment has been minimized.

Show comment Hide comment
@txdv

txdv Sep 21, 2012

Why the forum, discussions in here are far better.

txdv commented Sep 21, 2012

Why the forum, discussions in here are far better.

@erik-kallen

This comment has been minimized.

Show comment Hide comment
@erik-kallen

erik-kallen Sep 21, 2012

Contributor

The idea behind the forum is to give people a place to ask questions rather than sending them to me by email. I am not sure wether it's better to discuss features here or in a forum. Now I know your opinion, but I don't know your arguments.

Contributor

erik-kallen commented Sep 21, 2012

The idea behind the forum is to give people a place to ask questions rather than sending them to me by email. I am not sure wether it's better to discuss features here or in a forum. Now I know your opinion, but I don't know your arguments.

@txdv

This comment has been minimized.

Show comment Hide comment
@txdv

txdv Sep 21, 2012

Your binding prototype fits with the http.createServer API, but it is just a convenience method.

exports.createServer = function(requestListener) {
  return new Server(requestListener);
};

but there is also the class constructor function:

exports.Server = Server;

So proposal would be to map it like this:

public class HttpServer
{
    public HttpServer(Action<Request, Response> callback);
    public event Action OnClose; // and all the other events similar like this
    public void Close():
    public void Listen(int port);
    public void Listen(int port, string hostname);
    // etc
}

public static class Http
{
    public HttpServer CreateServer(Action<Request, Response> callback);
}

Both methods are now exposed. CreateServer returns an HttpServer, it is easy to chain with Listen than creating it with new:

(new Server(/* callback */)).Listen(8000);
Http.CreateServer(/* callback */)).Listen(8000);

That is why createServer exists.

Now in this particular case we would like to have an attribute for the static method to tell the compiler that it should load the http module with require.

[NodeModule('http')]
public static class Http

As well for the HttpServer

public class HttpServer
{
    [NodeModule('http', 'Server')];
    public HttpServer(Action<Request, Response> callback);

This is how I would do it for the blocking require version. I would also expose it in a stand alone version like this:

public static class Module
{
    public static object Require(string name)
    {
        // compiler magic
        return null;
    }
    public static T Require<T>(string name)
    {
        return Require(name) as T;
    }
}

So the user can have access to it. This class will come in handy when adding AMD functionality as well, because that one can't be implemented without an a function with a callback or at least the user should have the ability to use the function when he desires.

txdv commented Sep 21, 2012

Your binding prototype fits with the http.createServer API, but it is just a convenience method.

exports.createServer = function(requestListener) {
  return new Server(requestListener);
};

but there is also the class constructor function:

exports.Server = Server;

So proposal would be to map it like this:

public class HttpServer
{
    public HttpServer(Action<Request, Response> callback);
    public event Action OnClose; // and all the other events similar like this
    public void Close():
    public void Listen(int port);
    public void Listen(int port, string hostname);
    // etc
}

public static class Http
{
    public HttpServer CreateServer(Action<Request, Response> callback);
}

Both methods are now exposed. CreateServer returns an HttpServer, it is easy to chain with Listen than creating it with new:

(new Server(/* callback */)).Listen(8000);
Http.CreateServer(/* callback */)).Listen(8000);

That is why createServer exists.

Now in this particular case we would like to have an attribute for the static method to tell the compiler that it should load the http module with require.

[NodeModule('http')]
public static class Http

As well for the HttpServer

public class HttpServer
{
    [NodeModule('http', 'Server')];
    public HttpServer(Action<Request, Response> callback);

This is how I would do it for the blocking require version. I would also expose it in a stand alone version like this:

public static class Module
{
    public static object Require(string name)
    {
        // compiler magic
        return null;
    }
    public static T Require<T>(string name)
    {
        return Require(name) as T;
    }
}

So the user can have access to it. This class will come in handy when adding AMD functionality as well, because that one can't be implemented without an a function with a callback or at least the user should have the ability to use the function when he desires.

@erik-kallen

This comment has been minimized.

Show comment Hide comment
@erik-kallen

erik-kallen Sep 21, 2012

Contributor

Thanks for this lengthy post. So, what would the generated code look like for this:

(new Server(/* callback */)).Listen(8000);
Http.CreateServer(/* callback */)).Listen(8000);

?
Would it be something like

var $http = require('http');
new $http.Server(/* callback */).listen(8000);
$http.createServer(/* callback */).listen(8000);

?

Also, I think the NodeModule attribute would need to go on the type rather than the constructor. Right?

What compiler magic would you need in the Module.Require class?

And finally, how could attributes be created that would allow the generated script to be used as a node module? What would the generated code look like?

Contributor

erik-kallen commented Sep 21, 2012

Thanks for this lengthy post. So, what would the generated code look like for this:

(new Server(/* callback */)).Listen(8000);
Http.CreateServer(/* callback */)).Listen(8000);

?
Would it be something like

var $http = require('http');
new $http.Server(/* callback */).listen(8000);
$http.createServer(/* callback */).listen(8000);

?

Also, I think the NodeModule attribute would need to go on the type rather than the constructor. Right?

What compiler magic would you need in the Module.Require class?

And finally, how could attributes be created that would allow the generated script to be used as a node module? What would the generated code look like?

@txdv

This comment has been minimized.

Show comment Hide comment
@txdv

txdv Sep 21, 2012

Well, maybe someone wants to define his own constructors, so you need to select somehow the constructors which use the function constructor in javascript.
So you could do:

[NodeModule('http')]
public class HttpServer
{
    [ScriptName('Server')]
    public HttpServer(Action<Request, Response> callback) { /* empty body */ }

    [NonScriptable] // I hope this one is right
    public HttpServer()
    {
        // the user can do something that he wants to while reusing the other constructor
    }

So in conjuction with NodeModule the compiler will know that it has to call require('http').Server.

About the generated code now: I just tested

require('http') === require('http')

It returns true, so if one is really lazy, the code emmited could look like:

require('http').Server(/* callback */).listen(80000);
require('http').createServer(/* callback */).listen(80000);

But I would prefer your approach, but instead of a specific hard variable like $http, you would just use the next in line, $a, $b, $c

txdv commented Sep 21, 2012

Well, maybe someone wants to define his own constructors, so you need to select somehow the constructors which use the function constructor in javascript.
So you could do:

[NodeModule('http')]
public class HttpServer
{
    [ScriptName('Server')]
    public HttpServer(Action<Request, Response> callback) { /* empty body */ }

    [NonScriptable] // I hope this one is right
    public HttpServer()
    {
        // the user can do something that he wants to while reusing the other constructor
    }

So in conjuction with NodeModule the compiler will know that it has to call require('http').Server.

About the generated code now: I just tested

require('http') === require('http')

It returns true, so if one is really lazy, the code emmited could look like:

require('http').Server(/* callback */).listen(80000);
require('http').createServer(/* callback */).listen(80000);

But I would prefer your approach, but instead of a specific hard variable like $http, you would just use the next in line, $a, $b, $c

@txdv

This comment has been minimized.

Show comment Hide comment
@txdv

txdv Sep 21, 2012

I guess Module.Require would need only a simple mapping to the require method.

txdv commented Sep 21, 2012

I guess Module.Require would need only a simple mapping to the require method.

@txdv

This comment has been minimized.

Show comment Hide comment
@txdv

txdv Sep 21, 2012

My arguments to why github is better:

  1. It is a centralized place, no external stuff needed, the code is here, the issues and discussions should be here too.
  2. Everybody has or needs a github account anyway nowadays.
  3. There is no difference between the logging in process here and the forum.
  4. markdown instead of bbcode

Github has all this nice markup: http://github.github.com/github-flavored-markdown/
You can reference commits easy, add multiple showcases of code and stuff.
markdown is by far more easier to write than bbcode.
I was so irirated with bbcode, that I have created a markdown to bbcode converter

txdv commented Sep 21, 2012

My arguments to why github is better:

  1. It is a centralized place, no external stuff needed, the code is here, the issues and discussions should be here too.
  2. Everybody has or needs a github account anyway nowadays.
  3. There is no difference between the logging in process here and the forum.
  4. markdown instead of bbcode

Github has all this nice markup: http://github.github.com/github-flavored-markdown/
You can reference commits easy, add multiple showcases of code and stuff.
markdown is by far more easier to write than bbcode.
I was so irirated with bbcode, that I have created a markdown to bbcode converter

@dested

This comment has been minimized.

Show comment Hide comment
@dested

dested Sep 21, 2012

Ill chime in, I was the one who bothered Erik to create the forum. I didnt see it more for issue tracking, but for the developers who are beginning to use saltarelle on a more ongoing basis to communicate with other developers. That way the issue tracker doesnt get cluttered with "CODE WONT COMPILE" kinds of tickets. Also sharing projects and that kind of thing.

dested commented Sep 21, 2012

Ill chime in, I was the one who bothered Erik to create the forum. I didnt see it more for issue tracking, but for the developers who are beginning to use saltarelle on a more ongoing basis to communicate with other developers. That way the issue tracker doesnt get cluttered with "CODE WONT COMPILE" kinds of tickets. Also sharing projects and that kind of thing.

@txdv

This comment has been minimized.

Show comment Hide comment
@txdv

txdv Sep 21, 2012

Feature design is worth an issue imo.
The forum can stay for other questions, I guess I already did open an issue where I just asked for whats the difference between script# and saltarelle.
btw that name saltarelle was hard to remember.

txdv commented Sep 21, 2012

Feature design is worth an issue imo.
The forum can stay for other questions, I guess I already did open an issue where I just asked for whats the difference between script# and saltarelle.
btw that name saltarelle was hard to remember.

@erik-kallen

This comment has been minimized.

Show comment Hide comment
@erik-kallen

erik-kallen Sep 24, 2012

Contributor

For the github/forum question, we'll use github, at least for now and we'll see if the forum ever gets any user base.

Contributor

erik-kallen commented Sep 24, 2012

For the github/forum question, we'll use github, at least for now and we'll see if the forum ever gets any user base.

@erik-kallen

This comment has been minimized.

Show comment Hide comment
@erik-kallen

erik-kallen Sep 24, 2012

Contributor

For the differient constructors, we already have [AlternateSignature], which should probably suffice here. For the variable names I'm thinking (in non-minimized scripts) that it should try using the module name, but with fallback options if that name is already in use.

I guess the actual syntax for creating the server would be new (require('http').Server(/* callback */)).listen(80000);?

And, how do you think the generated script should look in order for the compiler output to be usable as a module?

Contributor

erik-kallen commented Sep 24, 2012

For the differient constructors, we already have [AlternateSignature], which should probably suffice here. For the variable names I'm thinking (in non-minimized scripts) that it should try using the module name, but with fallback options if that name is already in use.

I guess the actual syntax for creating the server would be new (require('http').Server(/* callback */)).listen(80000);?

And, how do you think the generated script should look in order for the compiler output to be usable as a module?

@txdv

This comment has been minimized.

Show comment Hide comment
@txdv

txdv Sep 24, 2012

A node module?
Are you talking about exports?

txdv commented Sep 24, 2012

A node module?
Are you talking about exports?

@erik-kallen

This comment has been minimized.

Show comment Hide comment
@erik-kallen

erik-kallen Sep 24, 2012

Contributor

Yes. But not about the syntax, but rather about the desired semantics. Eg, would we want something like

namespace X.Y {
    public class C {
    }
    public class D {
    }
}

namespace Z {
    public class C {
    }
}

to be translated to

/* stuff */
exports.X = {
    Y: { C:  X.Y.C, D: X.Y.D }
}
exports.Z = {
    C: Z.C
}

or something similar?

Or is there a better idea?

Contributor

erik-kallen commented Sep 24, 2012

Yes. But not about the syntax, but rather about the desired semantics. Eg, would we want something like

namespace X.Y {
    public class C {
    }
    public class D {
    }
}

namespace Z {
    public class C {
    }
}

to be translated to

/* stuff */
exports.X = {
    Y: { C:  X.Y.C, D: X.Y.D }
}
exports.Z = {
    C: Z.C
}

or something similar?

Or is there a better idea?

@txdv

This comment has been minimized.

Show comment Hide comment
@txdv

txdv Sep 24, 2012

Yeah, I guess that would be the solution.
It would be just like in .NET.
I guess one would reference the .net assemblies and the appropriate .js assembly would be in the same folder.

txdv commented Sep 24, 2012

Yeah, I guess that would be the solution.
It would be just like in .NET.
I guess one would reference the .net assemblies and the appropriate .js assembly would be in the same folder.

@erik-kallen

This comment has been minimized.

Show comment Hide comment
@erik-kallen

erik-kallen Oct 9, 2012

Contributor

The upcoming release 1.5.0 will support CommonJS modules (eg. Node) as well as AMD.

Contributor

erik-kallen commented Oct 9, 2012

The upcoming release 1.5.0 will support CommonJS modules (eg. Node) as well as AMD.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment