-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Consider adding a module level visibility modifier. #321
Comments
Would be good to see the motivating examples behind this and what JavaScript patterns this would help enable. Some general tips for proposals: https://github.com/Microsoft/TypeScript/wiki/Writing-Good-Design-Proposals |
I hear your call for more formal proposal. I will try my best as soon as I have time for it. Meanwhile here is an example. I want to get a lightweight implementation of a doubly linked list in TypeScript. The most lightweight version as far as I can see would be based on object literal rather than classes. It would look something like this:
Now notice that the internal properties like Notice the |
You can emulate this somewhat simply today: module Impl {
interface Node<a> extends Public.Node<a> {
left: Node<a>;
right: Node<a>;
}
interface List<a> extends Public.List<a> {
left: Node<a>;
right: Node<a>;
}
export function createNode<a>(left: Node<a>, value: a, right: Node<a>) {
var node = { left: left, value: value, right: right };
if (left != null) { left.right = node; }
if (right != null) { right.left = node; }
return node;
}
export function createEmptyList<a>() : List<a> {
return { left: null, right: null }
}
}
module Public {
export interface Node<a> {
value: a;
}
export interface List<a> {
/* empty */
}
export var createNode: <a>(left: Node<a>, value: a, right: Node<a>) => Node<a> = Impl.createNode;
export var createEmptyList: <a>() => List<a> = Impl.createEmptyList;
}
export = Public; One big advantage here is that the |
Such emulation solves only one half of the problem by hiding the internals. In your example the |
@mhegazy, @DanielRosenwasser, @RyanCavanaugh guys, is this |
no, it is an internal "hack", that we can remove with no warning. we are looking for ways to make this scenario supported. so stay tuned. |
👍 we need it 😊 |
related: #5228 |
How is work in progress with it? Or is there any work around? I wanted to use Namespaces but I can't because |
I just found that flow now actually supports opaque types aliases which from what I understand is very similar to my suggestions in #15408 and #15465. |
A related case that I stumble upon quite often is when I want to provide a base class for extension and that class contains members that are only used for collaborating with other code inside the module. // module utils.ts
export abstract class Base {
exposedA() { };
protected exposedB(): { };
/* module */ modulePrivate(): { };
}
// other code that calls Base.modulePrivate Currently, I have no way to do the above without enabling access to import { Base } from "./utils";
class Derived extends Base {
}
// here the internal details of the module are exposed via Derived.modulePrivate The crucial point here is that I want to enable the extension of a class. In cases where I don't want others to extend I just implement the class internally (without |
No amount of |
I recently found this article outlining how to approximate flow's |
This would be an extremely helpful addition to the language. It's one place where taking Javascript that was written using modules instead of classes becomes difficult to translate into Typescript. It would be great if it could cover opaque simple types like Some examples in the wild are the file descriptor in Node's In my experience the reasons I've had for using modules and interfaces instead of classes are
I wonder if the ability to specify interfaces for modules could help here, perhaps using |
+1 for using modules over classes :-).
You can sort-of do this today using plugin1.ts const firstValue = 11;
export function foo(x: string): number {...}
export function bar(x: number): string {...} plugin2.ts const secondValue = 22;
export function foo(x: string): number {...}
export function bar(x: number): string {...} main.ts import * as Plugin1 from "./plugin1.ts"
import * as Plugin2 from "./plugin2.ts"
interface Plugin {
readonly foo: (x: string)=>number;
readonly bar: (x: number)=> string;
}
const plugins: ReadonlyArray<Plugin> = [Plugin1, Plugin2]; This will give errors if a module does not I think having interfaces for modules would only cover exposing whole types or values? So in addition to that, to get field level privacy we need something like flow's |
@jonaskello That's pretty nice. It certainly allows you to selectively hide the stuff you want to export. I think I should have clarified that I meant a signature and not necessarily an counter.ts export type t = number;
export const make = () => 0;
export const incr = x => x + 1;
export const value = x => x; and then I would like to be able to have a description like (not real typescript): namespace /* or interface */ Counter {
type t; // The implementation of this type is not exposed.
make: () => t;
incr: (x: t) => t;
value: (x: t) => number;
} I don't want users of this Counter to be aware of the fact that it's implemented using a number. At the same time, I don't want to be forced to use a class to get this kind of information hiding. I suspect that allowing types to be declared inside of an If you're familiar with signatures (module types) from SML (Ocaml,) those are what I'm thinking of. |
First class support for existential types (#14466) might be another way to enable this kind of information hiding. |
@HarryGifford Yes that makes sense. I have only theoretical knowledge of ocaml/reasonml but I think you are refering the "Abstract Types" section of these docs. I think ocaml's module system looks really nice and if we could get that in typescript it would be great :-). In ES modules you by design couple the module signature to the implementation by decorating the implementation with the So we are really discussing two things here, abstraction of types and separate module signatures. The abstraction of types is solved in flow by use of opaque types. Using opaque types in the counter example above would look like this: counter.ts export opaque type t = number;
export const make = (): t => 0;
export const incr = (x: t): t => x + 1;
export const value = (x: t): t => x; I would rather have separate module signatures with abstract types like in ocaml, but I'm afraid that will be deemed too complex. The upside of opaque types are that they are very close to the original ES module design as they use the |
@jonaskello Yes. Abstract types. The two major things missing from Typescript that exist in Ocaml are abstract types and functors (functions from modules to modules.) The first could be implemented with the I think you're right that an @pelotom Are opaque types are a form of existential type? They allow you to define a set of operations on the type, without exposing the implementation of that type. EDIT: Looks like #31894 is exactly such a proposal. |
Yes: http://homepages.inf.ed.ac.uk/gdp/publications/Abstract_existential.pdf
Is it? It explicitly claims not to be related to existential types, and the primary use case seems to be about forward type declaration, not information hiding. |
@pelotom You're right. I misread the proposal. Thrown off by the exists keyword. |
Hello, here's an idea for thought: #35554. It may be more verbose (syntax is up for bike shedding), but the concept is to explicitly specify which code has access to protected or private members. Can that idea be expanded to pertain to various things, not just class members? For example, maybe a way to specify that an export is only available to certain other modules? I too think it'd be neat allow access modifiers on regular types and object literals, and to be able to pass those types around (#35416). |
Here's the workaround: Define the method using a symbol and don't export that symbol.
Since you don't export the symbol, it's not accessible externally. Downside is it still shows up. |
Another workaround is to create a new class variable and type that omit the unwanted properties:
|
A bit verbose maybe (could be made shorter with some helpers), but you can do this pretty neatly nowadays with static blocks. let getFoo: (instance: A) => string
let setFoo: (instance: A, value: string) => void
class A {
private foo: string = ''
static {
getFoo = (instance: A) => instance.foo
setFoo = (instance: A, value: string) => {
instance.foo = value
}
}
}
const a = new A()
a.foo // Not allowed
getFoo(a) // Is allowed Playground. |
Currently to protect integrity of data one can only use classes with private or protected fields. Classes are harder to work with compared to object literals. Being able to specify that certain fields are invisible to outside modules would be a valuable addition to support programming in functional style in TypeScript.
So what I am suggesting is to be able to specify whether a field of an interface is exported from a module or not. This also means that the instance of such interface can only be created within the module it is declared.
The similar features can be found in:
signature files
: http://msdn.microsoft.com/en-us/library/dd233196.aspxexport lists
: http://www.haskell.org/onlinereport/modules.htmlThe text was updated successfully, but these errors were encountered: