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

Proposal: new ES6 style syntax to apply typeof on an ambient external module #2357

Closed
rotemdan opened this issue Mar 14, 2015 · 4 comments
Closed
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds

Comments

@rotemdan
Copy link

As a continuation to #2346 I decided to re-target the discussion specifically with the new ES6 module syntax (#2242) in mind, and open it as a new issue (my apologies if I got some of the ES6 module semantics wrong here, as it's still very new to me).

I propose the following syntax to get the type of an ES6 (or the older variant of an) ambient external module, or some specific export of it. There are two versions, where the second one (my personal preference) is more succinct and readable as it omits the import keyword. Here are some examples (In many cases I'm still not sure of which version is "best" so I provided multiple alternatives):

The syntax (1st version)

Get entire namespace type:

// This looks straightforward:
type FileSystemNamespaceType = typeof (import * from "FileSystem");

Get specific export type:

// This seems straightforward
type FileReaderType = typeof (import FileReader from "FileSystem");
// Or maybe this would parallel the ES6 syntax better?
type FileReaderType = typeof (import {FileReader} from "FileSystem");

Get default export type:

// Straightforward (assuming "default" couldn't be used as a name of an ES6 export 
// there shouldn't be any conflict)
type FileSystemDefaultExportType = typeof (import default from "FileSystem");
// However ES6 spec puts the "default" keyword in curly brackets? - that's weird..
type FileSystemDefaultExportType = typeof (import {default} from "FileSystem");
// Or maybe this is better?
type FileSystemDefaultExportType = typeof (import "FileSystem");

Some import expressions don't really make sense when a (single) type is needed:

// The "as" doesn't make much sense here:
type FileReaderType = typeof (import {FileReader as MyFileReader} from "FileSystem"); 
// Multiple exports don't make much sense here:
type ImpossibleType = typeof (import {FileReader, FileWriter} from "FileSystem"); 

It would also be very useful to use it directly as a type within a regular variable declaration, like this:

var FileReader: typeof (import FileReader from "FileSystem");

I think this looks quite reasonable, and should be relatively easy to parse. Though it could be made even simpler..

The syntax (2nd version)

The import keyword could even be omitted entirely to yield a more succinct and natural syntax (that's still not very difficult to parse). Examples:

// Straightforward:
type FileSystemNamespaceType = typeof (* from "FileSystem");
// Straightforward:
type FileSystemDefaultExportType = typeof (default from "FileSystem");
// Or in curly brackets:
type FileSystemDefaultExportType = typeof ({default} from "FileSystem");
// Straightforward:
type FileReaderType = typeof (FileReader from "FileSystem");
// Or more akin to the ES6 syntax:
type FileReaderType = typeof ({FileReader} from "FileSystem");

Example use cases

Since typeof only gets the type information (or symbols) for the module exports, it should be usable in any scope, including within internal modules, functions, conditional statements or even loops. This would allow to attach type information to a dynamically or conditionally loaded module, such as in:

if (runningInNodeJS())
    var FileSystem: typeof (* from "fs") = require("fs");

or even used with a frequent CommonJS import pattern where only a specific member is dynamically loaded from a module:

if (runningInNodeJS())
    var fileReader: typeof ({FileReader} from "fs") = new require("fs").FileReader();

or used to load a replacement or polyfill for some module or export that has a different name, but conforms to the same type specification, essentially using the type as an interface:

var genericFileReader: typeof ({FileReader} from "fs");

if (usePolyfill)
    genericFileReader = new FileReaderPolyfill();
else
    genericFileReader = new require("fs").FileReader();

Your comments

I'd be interested in your opinions on this? and in case the language designers have different ideas for a syntax to get this sort of type information, I'd like to hear all about it!

@rotemdan rotemdan changed the title Proposal: new syntax to get typeof for an ES6 module import declaration Proposal: new ES6 style syntax to apply typeof on an ambient external module Mar 14, 2015
@rotemdan rotemdan mentioned this issue Mar 15, 2015
@ahejlsberg
Copy link
Member

We've heard requests for something like this many times now and it has a lot of merit. I would aim to keep it a relatively simple feature, basically an import "operator" that can be used with typeof:

type FileSystemType = typeof import "FileSystem";

We already allow dot to select members in a typeof argument. I think we should stay with that, but perhaps require parens around the import operator to make the precedence look right:

type FileSystemDefaultExportType = typeof (import "FileSystem").default;
type FileReaderType = typeof (import "FileSystem").FileReader;

@rotemdan
Copy link
Author

I think that looks quite reasonable (not sure if typeof (import "FileSystem").* would be valid but that also looks acceptable). I would personally prefer to use typeof (import "FileSystem") (with the parenthesis) in the first example though, as it would appear clearer to me (might not be everyone's preference). One problem may be that it is ambiguous between a default and a * import, but I'm not sure if that's a significant issue (the whole ES6 module design is still new to me, so I'm uncertain to what degree the concept of default imports would actually be useful or common in practice, I mean, perhaps in most cases a default import would be the same as a * import anyway?).

However, have you considered using the module keyword instead of import though? I think it would be clearer (module "FileSystem" also parallels the ambient external module declaration syntax, so it intuitively connotates a more abstract and type-oriented semantics), e.g.

type FileSystemType = typeof module "FileSystem"; // <-- looks good to me even without the parens

type FileSystemDefaultExportType = typeof (module "FileSystem").default;
type FileReaderType = typeof (module "FileSystem").FileReader;

The problem with import is that in English it is more commonly used as a verb (though less frequently as a noun, but I don't think most people would read it this way here). An analogous English expression would read like, for instance

"get the type of (take [the] box), choose [the] shoe"

(your proposal), or

"get the type of (take [the] shoe from [the] box)"

(my first version), instead of the more grammatically correct

"get the type of ([the] shoe from [the] box)"

e.g. the syntax:

typeof (FileReader from "FileSystem")

(my second version), though under the mind-boggling ES6 semantics, FileReader, without curly brackets, would actually imply a default import here! (also, viewing it like this, it seems that the word in may read less ambiguously than from here, but wouldn't work that well with default and * though).

Nevertheless having the module name first may be a big advantage for editor auto-completion and would allow quicker type-checker feedback on the module name, so I would understand why an alternative might be preferred (and now that I considered your approach, I actually like it better, but only when used with the module keyword, as I mentioned above).

Anyway, whatever syntax is chosen, I'm glad this feature is being considered as being able to easily extract types from external module declarations would be a highly useful feature for myself, and possibly others. I develop libraries that are mostly targeted to work both on the web and in Node, so I almost always load modules conditionally and at an as-needed basis (a pattern that is very frequent in CommonJS development - even the Node manual uses it extensively). And although having spent much time using languages like C# and C++, it seems that being imposed an (artificial, in many cases) requirement to "statically" declare module imports ahead of time has began to appear a bit outdated and rigid to me, and frankly not very characteristic of the more dynamic style of JavaScript.

Seeing the freedom and flexibility the dynamic approach gives, I would actually go as far as saying it would be interesting to consider [TypeScript] allowing modules to be ["officially"] imported everywhere, including, for example, in conditionals and loops (CommonJS/Node has a caching system, so the module will only be loaded once), however, ES seems to have gone in a different direction, but again I don't really have the knowledge and foresight to analyze that right now as I currently don't know a lot about it (and of course, haven't really used it practice).

@rotemdan
Copy link
Author

I tried to write a proposal to continue the (diverging) discussion in a different issue (mostly to express my ideas in a more coherent and organized way and to offer some of my own suggestions).

See:
"Proposal: extend legacy module import syntax to a fully dynamic module loader with polyfills" (#2508)

@RyanCavanaugh RyanCavanaugh added Declined The issue was declined as something which matches the TypeScript vision Too Complex An issue which adding support for may be too complex for the value it adds and removed In Discussion Not yet reached consensus labels May 4, 2015
@RyanCavanaugh
Copy link
Member

Need more compelling use cases than "not wanting an intermediary import" to justify complicating the syntax here.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds
Projects
None yet
Development

No branches or pull requests

4 participants