-
Notifications
You must be signed in to change notification settings - Fork 36
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
Why not check classes? #20
Comments
Hi @jtremback Consider the following two files: // a.ts
class A {}
export interface B {
a: A;
} // b.ts
import {B} from './a';
import {is} from 'typescript-is';
is<B>({}); When I generate the code I can easily output something like If you can think of something, that would be great :) |
@woutervh- Export something in that same file which is the |
Thanks for the suggestion.
In general the problem is that I have to reference something at run time which can't (or with great difficulty) be guaranteed to have a reference to. |
Closing for house keeping. |
I know this is old, but: An alternative syntax would be |
Re-opening for discussion. Hi @LoganDark Maybe it could work in some cases... can you show in code how you would do this? const classGenerator = (/* ... */) => {
return class { /* ... */ };
}
const y = new (classGenerator(/* ... */))();
// Later on in the code...
is<typeof y>(y); // should return `true` - but what will you write here: y instanceof ?? Maybe the better trade-off is to assume that the name of the class is available within the scope of the call to ...
{
...
is<X>(x); // will be implemented with `x instanceof X` - requires that `X` is available in the scope.
} It will probably cover most cases I think, usually classes are used in such a way that the name is available in the scope, e.g.:
Like mentioned before, it will work as long as the actual class is available at the call site as-is. |
Nevermind, it's just all the symbols that were confusing me. It's just a function that returns an anonymous class. But, uh... TypeScript generates temporary variables all the time. Of course it would make sense to auto-generate variables for every argument/whatever to stop it from getting reevaluated tons of times. So, I dunno.
((value) => {
const temp1 = typeof y
// ...
if (!(value instanceof temp1)) return false
// ...
return true
})(y) The fact that you're checking that something is an instance of itself is programmer error, not typescript-is error. I'm not sure why you'd need to check if
|
By the way, that's not exactly what was intended... I think my original (right after issue close) post was a bit weird, but... class SomeClass { /* ... */ }
interface Thingy {
someClass: SomeClass
// ...
}
is<Thingy, SomeClass>({someClass: SomeClass()}) This is what I mean. Optionally: is<Thingy>({someClass: SomeClass()}, SomeClass) however, this would break many many things, especially since you can't infer which types to generate instanceof checks for without the function being called immediately (therefore making it impossible for the programmer to say, for example, Of course, there's always a case like this: interface Thingy {
class: SomeClass
class2: SomeClass2
}
// in a scope without SomeClass2...
is<Thingy, SomeClass>({class: SomeClass(), class2: (class {})() /* haha */}) where the Built-in classes like Date and RegExp should always have instanceof checks generated, if you're paranoid about this breaking existing code make it an opt-in config option. |
Hi @LoganDark The point I was trying to make with const classGenerator = (/* ... */) => {
return class { /* ... */ };
};
const y = new (classGenerator(/* ... */))();
function example (arg: any) {
is<typeof y>(arg); // Find implementation for `is<typeof y>`
} The idea of putting The idea of putting the runtime classes as an argument to the interface Thingy {
class: SomeClass;
}
// We should require that the 2nd argument is really of type `typeof SomeClass`
is<Thingy>({}, SomeClass); // Allowed!
is<Thingy>({}, SomeOtherClass); // Not allowed! And I'm also wondering how to determine the order of the parameters or the structure of the parameters which supply the runtime classes. |
Your first code block is still trivially solvable by putting What I'm trying to get at with Thinking a little further, I believe you should put that variable right outside the function so it isn't re-evaluated every time it's called. You can use regular old TypeScript knows the type of pretty much every expression, so with the template-argument syntax (which basically configures the function's structure before it gets called!! which is important) you can easily verify that the passed classes occur somewhere within the interface you're checking for. |
Hi there, Would you be open to reconsider this entire argument from scratch? tl;dr: Just have I think you've got the concept of In this expression,
In this expression however,
In the latter, it doesn't matter if This is what it means for TypeScript to be a structurally typed language. The The variable is the constructor. The type is the instance type. Effectively we have: const Foo: typeof Foo = { new(): Foo } The type An instanceof check for If this library should have an instanceof utility (maybe it should not, but let's assume it should for the sake of argument), then it should be a different operator than the operators that check for interface types. is<Foo>(bar); // Structural checking
isInstanceOf(Foo, bar); // constructor checking I am not even suggesting the syntax Now let's go back to our inital question: what to do with classes? It should make no difference if is<Foo>(candidate) I was surprised to see that typescript-is choked on this code. As a matter of fact I wasn't even aware that the type I was checking was a class type. I thought it was an interface. The point is: it should not even matter if it's a class or an interface. This is TypeScript, only the structure matters. Sorry for the wall of text. This discussion has already been going on for a while and I figured I'd need to bring serious arguments to the table in order to change your minds. Bottom line is: Can we just have classes work the same way as interfaces? |
Hi @hmil I think the suggestion you propose to treat it the same as an interface, and not bother with What do you think of these options?
I'm wondering what everyone else thinks of this, because in my mind I don't want to give users the false impression that it will check for classes, and then it turns out that the user thought they had a certain class, but they don't. |
Either option 2 or 3 seem good to me. Or maybe 3, but give an option to turn off the warning for people who "know what they're doing". One major use case I did not cite in my message above is that Java devs coming to TypeScript will tend to use classes as DTOs (because they're used to POJOs). This situation seem to happen quite often, at least in my experience. Obviously it'd be great if other people could comment on this issue to give their opinion. |
In my opinion, but actually nobody want to treat non-Foo object as Foo instance. If we really want, we can create new interface extending the class. It's enough to make possible to accept such interfaces for typescript-is. I think it's better to crash when it face classes, because it's bigger problem that I cannot notice the type is including classes. I +1 the solution giving class as arguments. like is<Thingy>({}, SomeClass); Or using decorator like other library is also good solution. interface Thingy {
@classType(SomeClass)
someClass: SomeClass;
} |
Option 2 sounds best to me (though option 1 and 3 are also fine). The reason I don't have issue with Granted, my "natural assumption" described above is apparently not shared by all developers (eg. @acomagu), and I can understand why. Thus, I am fine with any of the three options -- just so long as there's a way to achieve the described-above behavior in a project-wide/set-it-and-forget-it way. (though option 2 is still my preferred, with just a note in the readme saying that if you use the EDIT: And for people who want to make sure an object is matching both by shape and prototype (if option 2 is implemented), they can just do: This gives them higher assurance that the object will interact as expected, since not only is the prototype correct, but the properties are also the correct types and shape. This level of assurance is not possible by merely inserting an |
"This library will not check classes. Instead, you are encouraged to use the native instanceof operator."
Wouldn't it be easy to have this library generate the instanceof code? What's the reason not to?
The text was updated successfully, but these errors were encountered: