-
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
Suggestion: Module level privacy for types #15465
Comments
dup? #321 |
@Aleksey-Bykov Yes, it is definitely a dup of #321. And I made this suggestion for the same reason you did :-). I originally searched for "abstract type" and "opaque type" and ended up empty, that's why I did not find your suggestion. I changed the |
i'd keep this one open so the problem gets more attention |
@Aleksey-Bykov It seems the typescript team is reluctant to add this. And I can understand why. In my experince, when you maintain a product, the cost of adding a feature is low compared to the cost of maintaining the added complextiy it incurs in all future development. I guess the main userbase of TS are mainstream OOP style programmers. So adding features for a low (but hopefully growing) number of TS users doing lightweight FP style development with modules has low business value. Nevertheless, I think this suggestion is still valid for the proposed use-case, even if that use-case is currently a bit narrow. I guess we'll have to see if there are other mainstream OOP use-cases that it can solve or if more developers adopt ES modules for FP style programming and would benefit from having the compile-time checks this suggestion enables. |
So the main problem here is that if you ask 10 developers what constitutes a module-level boundary, you'll get 11 answers (namespace! compilation unit! folder! ES6 module! file! npm We're sympathetic here -- we use a weird This all gets even worse because you actually do need to emit "internal" things, sometimes, to prevent incorrect assignability or derived class private field overwrites. Unfortunately I don't have an authoritative "you should do X instead" here because I think it really depends on what the use case is. |
having 11 questions and giving 1 COMPLETELY UNRELATED answer never stopped you before (#13002) did it? how come you are all lost now? |
i love it! can i be your friend please! please! |
@RyanCavanaugh I think "module" has a distinct meaning within ES and by extension also TS. But I see what you mean. I have a need in some projects where I have a monorepo which contains "libraries" that have internals and a public API. For this I made a linting rule to only allow import of Although there seems to be different needs for boundary (namespace, compilation unit, folder, ES6 module, file, npm @ scope, tsconfig! ...) I would think all of them could be boiled down to having a boundary for a set of files? Because the needs are different, I guess one solution would be to make it possible for the end-user to define sets of files himself, perhaps in tsconfig. Then there would be no need to decide on a common definition of the boundary for |
Module level privacy for types
It is sometimes useful to declare a type within a module and then have that module handle all of the operations on that type. This way the consumers of the module will not know the internals of the type and cannot couple themselves to its properties. But we still get type safety when calling the module's functions. This is especially useful in a FP programming style where we only use functions and types packaged into modules.
Goals
To make the internal of types private to the module they are declared in.
Non-goals
To introduce nominal typing in order to solve the goal.
Example scenario
Consider a
date.ts
module that handles aDate
type. The internals of theDate
type is private to thedate.ts
module. This is declared by amodule
modifier on theDate
's members that this suggestions adds support for. But the type is still exported so other modules know of theDate
type. This means type-safety is possible when calling the methods of the Date module.date.ts
Consumption
Usefulness
Using this approach it will become much easier to refactor the
Date
type. Let's say we decide it is better to have it store ticks since epoch:Without privacy, other modules may have directly read the
Date.years
property instead of going through ouryear()
function making our refactoring hard.Similar features in other languages
Ocaml: abstract type
Reason: abstract type
Elm: opaque type
F#: signature files
Haskell: export lists
Work-arounds
Class
It has been noted that in typescript
class
can be used to emulate the idea of this proposal. But that is only true because typescript adds theprivate
modifier toclass
. In plain JS/ES it would not make sense to use both a module and aclass
as modules alone provides an idiomatic construct for packaging static functions together. So usingclass
in plain JS would only add another level of nesting/complexity/syntax without adding any value.Consider this plain JS/ES module:
date.js
In the above module it does not make sense to use
class
because in JSclass
is neither immutable nor has the ability to be opaque. Typescript currently has the ability to express all the notes in the comments except the "considered to be opaque" part. For example "considered to be immutable" could be expressed asreadonly
on the type's properties. This proposal is about being able to express the "considered to be opaque" part in the typescript type system without having to modify the original JS program to useclass
.Another big disadvantage of using
class
as a data record is that it cannot surviveJSON.stringify()
,JSON.parse()
round-trips. This makesclass
impractical to use in many real-world scenarios such as for types in a serializable Redux state, or any types you want to put in local storage, or send over the network.The closest
class
work-around that is available in TS today is what @gcnew describes here. This example also survives round-trips. However it requires us to declare a class and take special care to not use all class features. It also requires code to re-export all the class methods from the module. So it involves quite a lot of syntatic work-around noise compared to the idiomatic JS example above. This suggestion makes it possible to write idiomatic JS instead of adjusting the JS to fit the current privacy features in TS.Prefix
Another suggested work-around is to prefix the fields in the record with
_
in order to mark them as internal. This work-around is much better in the sense that it does not alter the way you write the code. But there are two drawback. The first being that obviously this would not enable any compile-time checks. The second being that although_
is a convention commonly used for private members of aclass
in OOP style programming, there is (AFAIK) no such tradition for FP style record types. So the use of_
in this case becomes unconventional.Related suggestions
#321 is an earlier suggestion for the same thing.
Abstract/opaque types was originally proposed in #15408 which has relevant discussions leading up to this proposal.
The
internal
modifier in #5228 is similar. It adds privacy on the package level. If each module was a package andinternal
was available for types in addition toclass
then it would be identical to this suggestion.The text was updated successfully, but these errors were encountered: