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

Generic in/out type format, with implicit gaurds. Support inline mutation/delete,updating #27527

Closed
4 tasks done
wesleyolis opened this issue Oct 3, 2018 · 4 comments
Closed
4 tasks done
Labels
Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds

Comments

@wesleyolis
Copy link

Search Terms

mutation, reference parameter mutation, delete, old js assignment.

Feature:

The ability to support in and out type formats for generics and output guard formats,
which would allow one to handle all inline mutations of type with assignment in js assignment and
function like delete, which mutate the type keys. Typically many function mutate the structure, in which they don't use the copy on write principle or c++ const protection as not available.
This is simple proposal to accommodate all the required cases:
This is a suggestion, the final form of this may differer.

interface A {
    a : Date;
    b : number;
    c : string;
    d : boolean;
}

const record : A = {
    a : new Date(),
    b : 1,
    c : "str",
    d : true
}

delete record.b;

// Shouldn't be accessible anymore.
record.b = 5;

// Example function signature as of sorts.
function deletefFun<This extends Record<string, any>, Key extends keyof This>(object : This, key : Key) : undefined | Key
{
    if(object[key])
    {
        object[key] = undefined; //deleted
    }   
    else
    {
        return key;
    }
}

Generic Type support for Reference in/out

Require function signature to support type in and out, where the structure is internally mutated ( reference parameter) Currently ts assume that all input params will not be mutated any future.
This couple with a implicitly guard functionality for mutable ref tables, would ensure
that there can be no errors in ones coding, or require work around, everything should just work.
If inline mutation of a type is support then the output format could be compute automatically.

function deletefFun<{in:This extends Record<string, any>,out:PickInverse<This,key>}, Key extends keyof This>(object : This, key : Key) : undefined | Key
{
    if(object[key])
    {
        object[key] = undefined; //delete
    }   
    else
    {
        return key;
    }
}

Generic Type with Reference in/out and implicit Gard

This would be a function with a simple implicit boolean guard format, which format that should be compatible with union kind.which would then support multiple implicit type guards.

function deleteFunGaud<{
    in: This extends Record<string, any>, 
    gaurd: {
        undefined:PickInverse<This,key>, 
        [Key]:Key
    }
    ,Key extends keyof This>(object : This, key : Key) : undefined | Key
{
}

Yes, in this example on the mutation is required, if because one is able to ensure that the key exists, however, if this statement could fail for any other reason or throw and error, then the type guad, would work perfectly. some underlying memory allocation failed. Some thought into the use with catch, statement to know the final mutated form, before that throw, were should be a subset of existing fields, which have not been mutated in the try, as we don't know the state until runtime and guards should need to be used for all those parameters in the catch statement, maybe a shorthand inline guard, would be good if go the catch support route too.

Examples use of functions In/Out and Guards

Exmaple 1:

deleteFunGaud(record, 'b') // results is undefined | 'b'

record.b    // is of type Error('Implicit guard check is missing, not can't be know, without runtime check')
// or 
record = // This should reset the reference, back to a form that match existing type prior to mutation.

Example 2:

if (deleteFunGuad(record, 'b'))
{
    record.b // undefined and is not available, it has been removed.
}
else
{
    record.b // Is defined, because delete failed.
}

This also becomes a problem when one wants to mutate and object in old school style
style, one needs to use the spread operator or Object.assign(..) to mutate the type,
with copy on write principles alot of existing code mutates the object structure after a pull from a database. Most of the code, must now be formatted in the copy on write principle.
This is true for most all code, were results are returned and additional information is tack onto the existing database return structure. It would be nice, if index operator could automatically mutate the types, when executed in a block(body) of code.

InlineMutation

function fun(paramA : A){}

record.p : string = 'p';
record.z = 'z' as string;

fun(record) // error type mismatches, as it has been mutated.

fun({} as A) // Good

record = {} as A // fails, type mismatches as it has been mutated.

Other Related Issues:

#13783
#13195

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. new expression-level syntax)
@ghost
Copy link

ghost commented Oct 3, 2018

This is too complex because it violates a basic assumption of the type system, that the value of an object may change but the type should stay the same.
If we allow this, they any call expression may be changing the types of its inputs. Currently we can check any expression independently, looking only at its type declaration (and type-narrowing control flow, which is much simpler than looking everywhere for side effects).

function f(x: SomeType) {
    .... // A bunch of code that we ignore when determining the return type of `f`
    return x; // We assume this is still SomeType
}

But if functions could mutate the types of their parameters, then we would have to look at every call expression that came before then to see if it has an effect, which would slow things down too much.

@ghost ghost added the Too Complex An issue which adding support for may be too complex for the value it adds label Oct 3, 2018
@RyanCavanaugh RyanCavanaugh added the Suggestion An idea for TypeScript label Oct 3, 2018
@wesleyolis
Copy link
Author

@andy-ms @RyanCavanaugh Well typing system is to make sure that the shapes and forms of everything match, doesn't mean this is not suitable for a typing system. I would just say its more complex, because you have to evaluate the body of the function, all shapes on function calls, will still have to match. If it were mutated it may no longer match anymore, communicating incorrect mutation.

What if only the part of this concept that is implemented is the generic in/out, guard(out) at generic function level. No internal mutation inside the body of a function anything complicated like that. You still able to do as you said, know the shape of the input and output form would be know at the function level. But yes, you still have handle mutation at a function level. Which should just be type object update after the types of function have been evaluated. Most functionality should exists, just wrapping it in a in/out and then update the input type after the function evaluation in the compiler with resolved out type.

Would you be open to pull requests for the feature?

@RyanCavanaugh
Copy link
Member

Would you be open to pull requests for the feature?

"Too Complex" doesn't mean we think it's too much work for ourselves to do; it means we think the complexity it adds to the language and codebase is not justified by the value it would provide.

@wesleyolis
Copy link
Author

Well, does the TS community have any other suggestions on how to handle passing object by reference to function, were the function internally, can mutate the the typescript shape. As I understand, the goal for TS is to be a super set of JS, lacking this form of mechanism, regardless of the syntax complexity, means typescript is not a full super set of JS and doesn;t support all the mechanism patterns required.

I would really like to here what other ideas, there the ts community has for solving this.

Who makes the final calls on these things, because there is a large community and at hand?
I could present many different cases that are potentially pitfalls, were typescript is not actually type shape safe, its only type safe. We need to look at addressing the Shape safe capabilities of typescript,
which were the pitfalls still are.

When it comes to complexity, I would prefer a super set with typings to support the all the subset of commands, regardless of complex, completeness over complexity. There would be few that would write typings potentially for these method, so the complexity could be handle by the more advanced users, in my opinion. Right now, if one is to use a delete method as an example, typescript, would say everything is good, but in actually fact, downstream of any of references changes, things are not all great. typescript could be said that it actually lying to one. as the key/item is actually missing from the interface. We can write wrapper function that ensure the key exists, which means the return results will be updated, which one can continue to operator on for this case it would work.

One needs to look at the general case, in which all methods that can remove keys and change the shape, should be disallow, because they can operator on function parameter, resulting in mutation and typescript wouldn't be anywise, thinking that everything is hunky dory, while in reality it is not.

But disabling this type of function that allows mutation of the shape, would also rendering alot of functionality useless, returning us to the dark ages.

Alternatively one could look at implementing const version just like c++ of has, however, in this cases if means the input shapes for the parameters is constant, and non of the shape mutating, methods can be used in the body of the function.

If a param is not marked as const, then it is a ts error to attempt to use the variable given to a function after the function call. to use the results would force one to return the newly mutated parameter and continue operation on the return parameter under a new variable name in the parent scope.

const would become rather, verbose, so it may be better to look at inverse keyword, but this would be breaking changes to existing code, causing mass refactors potentially. So opt in of const would be good, were const is missing, on just generating compiler warnings which can be turned on or off.

One thing to remember, is that changing the shape, in TS, should not force one to have efficient statements, that results in the object being copied all, to drop or add an existing key. One will be forcing everyone to take a performance knock.

At the end of the day, a discussion needs to be had on how TS is going to align itself with JS, reading mutation of the shape, with regards to pros, crons of what regarding as acceptable performance degradation, added complexity, loss js functionality, just working out the box, like is subset language.
I feel that a discussion about this matter is the only way forward, @RyanCavanaugh, @andy-ms!
Is the community open to a discussion, I can provide many pity falls cases if required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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

2 participants