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

Omitting generics from the end is not allowed #3448

Closed
Ciantic opened this issue Jun 9, 2015 · 8 comments
Closed

Omitting generics from the end is not allowed #3448

Ciantic opened this issue Jun 9, 2015 · 8 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Duplicate An existing issue was already created Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@Ciantic
Copy link

Ciantic commented Jun 9, 2015

I don't see why omitting (or leaving remaining generics for type inference) is not allowed:

interface SomeInterface<M, T> {
    other: T
    validate(): M
}

function test<M,T>(t: T): SomeInterface<M, T> {
    return <any> null;
}

interface Thingie {
    first: string
    second: string
}

test<Thingie, string>("test"); // Works just fine
test<Thingie>("test"); // ERROR: Supplied parameters do not match any signature of call target.

Now this is trivial in case of string, but if the argument is something like object literal, then I have to retype everything from that object literal to two places.

Another syntax proposal could be allowing have empty comma, like this: test<Thingie,>("test");

It would signify that second parameter is left empty, intentionally.

@mhegazy
Copy link
Contributor

mhegazy commented Jun 9, 2015

The compiler can not know the intention. so either you left it off by mistake or intentionally to mean any. The design favors conservativness here. also the compiler will try to infer the generic type arguments from your normal arguments. the example above is a bit artificial though, as the type parameter M is not manifested in any argument, but rather passed through to the return type, which is identical to casting the result <SomeInterface<Thingie, string>>test("test");

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Jun 9, 2015
@mhegazy mhegazy closed this as completed Jun 9, 2015
@Ciantic
Copy link
Author

Ciantic commented Jun 10, 2015

It's not artificial example, I'm creating a validator and it requires me to type both generics even though the second one is always much better as type inferred.

It's hard to explain this succintly, but I suspect there is whole lot of other use cases for leaving rest of the generics for type inference.

Did you look at this edit I added: the intentional syntax test<Thingie,>("test"); makes it clear programmer actually wants the second argument inferred.

@danquirk
Copy link
Member

Could you post an example of code where you need to explicitly specify some type arguments which aren't inferred correctly while others are? The better fix from my perspective would be to fix the inference process so that you don't have to specify any of the type arguments explicitly.

@Ciantic
Copy link
Author

Ciantic commented Jun 11, 2015

@danquirk and @mhegazy

This example would be prohibitively long, so I'll try again with use case. But the use case is so cool and useful that I really want this to work! This is basically Django class fields syntax as object literal, and with interfaces, no classes was abused!

It allowes to create nested validator in fully readable format, check this out:

interface Address {
    city : string
    street : string
}

interface User {
    id: number
    name: string
    email: string
    address: Address
}

var userValidator = V.objectField<User>({ // Here is the GIST of the problem!
    id : V.integerField(),
    name : V.stringField(required=true),
    email : V.stringField(),
    address : V.objectField<Address>({
        city : V.stringField(),
        street : V.stringField(),
    })
});

userValidator
    .validate({...}) // Some User object from FORM data etc. you want to validate
    .then(function (valid) {
        // Valid is User!
    })
    .catch(function (errors) { // errors is {[name: string} : string[]}

    });

// Or I can fully typedly validate part of the tree:
userValidator.$.address
    .validate({...}) // Address
    .then(function (valid) {
        // Valid is Address!
    })
    .catch(function (errors) { // errors is {[name: string} : string[]}

    });

// or nested
userValidator.$.address.$.street.validate(...) // This *is* same stringField as in the definition

Get it? The V.objectField is the test function in my first example. It passes the User to interface, it doesn't use it as argument at first.

The use case I have here is really amazing! Unfortunately to get this thing working right now I have to write it something like this :(

var userValidator = V.objectField<User, {
    id : IntegerField,
    name : StringField,
    email : StringField,
    address : ObjectField<Address, {
        street : StringField,
        city : StringField,
    }>
}>({ // Here is the GIST of the problem!
    id : V.integerField(),
    name : V.stringField(required=true),
    email : V.stringField(),
    address : V.objectField<Address>({
        city : V.stringField(),
        street : V.stringField(),
    })
});

Because if I put that User in there it requires me to to insert the second parameter too.

Edit IF you are wondering why the second generic is there at all it's because it has important use cases. E.g. I have special variable $ I have added it's use case to above example.

@mhegazy
Copy link
Contributor

mhegazy commented Jun 12, 2015

looks like this is a dupe of #3423 then?

@Ciantic
Copy link
Author

Ciantic commented Jun 13, 2015

In my case the User is not inferrable from anywhere, it's tight to the validator object when validator is defined. I mean this:

var userValidator = V.objectField<User>({ // Here is the GIST of the problem!
    id : V.integerField(),
    name : V.stringField(required=true),
    email : V.stringField(),
    address : V.objectField<Address>({
        city : V.stringField(),
        street : V.stringField(),
    })
});

Can't be simplified anymore in any way. It already contains everything it needs to. This "User" can't be inferred from anywhere. Because the above syntax defines the validator for User, inferring it when using the validator would be dangerous because the point is to validate User and not arbitrary objects.

But I do agree however that maybe optional generics is not a good thing and I propose either alternative syntax:

var userValidator = V.objectField<User,>(...) or something like it var userValidator = V.objectField<User,*>() to signify the programmer wants the generic to be inferred.

@mhegazy mhegazy reopened this Jun 13, 2015
@mhegazy mhegazy added Suggestion An idea for TypeScript and removed Question An issue which isn't directly actionable in code labels Jun 13, 2015
@Ciantic
Copy link
Author

Ciantic commented Jun 27, 2015

Edit I must say this is a hack, and not a real solution. It is awful to use, I've tried really hard.

I just discovered a way to "omit" generics using a function, that returns a function:

// Trick is the definition in here:
function objectField<C>(v: C): <M>() => ObjectField<M, C> {
    return <any> null; // This implementation is not relevant
}

// Now I can define it neatly as two, with this first one type inferred, and second one as typed
var validator = V.objectField({
    id : V.numberField(),
    name : V.stringField(),
    addresses : V.arrayField(V.objectField({
        id : V.numberField(),
        street : V.stringField(),
        city  : V.stringField()
    }))
})<{
    id : number,
    name : string,
    addresses : {
        id : number,
        street : string,
        city : string
    }[]
}>();

validator.$.id // Auto completes as id, numberField
validator.validate(<any>{}).then(v => {
    v.id // Auto completes as id, number
});

@mhegazy mhegazy added the Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. label Dec 10, 2015
@mhegazy
Copy link
Contributor

mhegazy commented Feb 22, 2016

#2175 provides a safer way to achieve the desired output. the declarations will need to opt-in into this by specifying a default type.

@mhegazy mhegazy closed this as completed Feb 22, 2016
@mhegazy mhegazy added Declined The issue was declined as something which matches the TypeScript vision Duplicate An existing issue was already created labels Feb 22, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
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 Duplicate An existing issue was already created Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants