Skip to content

Guarded function declarations #9253

Closed
Closed
@tinganho

Description

@tinganho

I sometimes reference outer scope variables and class members in my function/method declarations. The problem is that those references are nullables. I want a way to ensure that a function is called on a call site where those references are not null.

let corns: string | null;
function makePopCorn(): string 
    requires corns !== null
{
   return 'pop';
}
if (corns) {
   makePopCorn();
}
makePopCorn(); // error corns can be null

Related #198. But not duplicate.

Activity

aluanhaddad

aluanhaddad commented on Jun 19, 2016

@aluanhaddad
Contributor

If makePopCorn dereferenced corns enabling structNullChecks would require a check inside it. I assume the requires clause to be lexically scoped so why should there be an external check.
Or is the intent to make the function only callable based on arbitrary restrictions on members in its declarations scope that it specifies declaratively? That would seem like an odd coupling and if the function were passed as a callback, corns would not be available to the caller. Is the intended behavior in that case that an Error would be thrown at runtime?

tinganho

tinganho commented on Jun 19, 2016

@tinganho
ContributorAuthor

Or is the intent to make the function only callable based on arbitrary restrictions on members in its declarations scope that it specifies declaratively

Yes this was my original intention.

That would seem like an odd coupling and if the function were passed as a callback, corns would not be available to the caller.

I'm not sure I understand you. Why would the caller need corns? Assuming caller is the function that calls the callback.

If the functions is being passed as a callback corns still needs to be checked that it is not null.

let corns: string | null;
function makePopCorn(): string 
    requires corns !== null
{
   return 'pop';
}
function call(callback: any) {
   callback();
}
if (corns) {
   call(makePopCorn);
}
call(makePopCorn); // error corns can be null
aluanhaddad

aluanhaddad commented on Jun 19, 2016

@aluanhaddad
Contributor

If the functions is being passed as a callback corns still needs to be checked that it is not null.

let corns: string | null;
function makePopCorn(): string 
    requires corns !== null
{
   return 'pop';
}
function call(callback: any) {
   callback();
}
if (corns) {
   call(makePopCorn);
}
call(makePopCorn); // error corns can be null

But that has a different meaning. It means don't allow makePopCorn to be passed as an argument when corns is null. That does not mean that corns will not be null when it is invoked.
Just as an arbitrary example:

let corns: string | null;
function makePopCorn(): string 
    requires corns !== null
{
   return 'pop';
}
function callLater(callback: any) {
   setTimeout(() => callback(), 1000);
}
if (corns) {
   callLater(makePopCorn);
}
corns = null;
tinganho

tinganho commented on Jun 19, 2016

@tinganho
ContributorAuthor

That does not mean that corns will not be null when it is invoked.
Just as an arbitrary example:

Yes I know. Your example have been covered before in the topic of callbacks in strictNullChecks. And I think it is an ongoing issue. But I have proposed to declare callbacks as immediately invoked or not in #7770 (comment).

aluanhaddad

aluanhaddad commented on Jun 19, 2016

@aluanhaddad
Contributor

OK but then why not just check inside the function? The questions of immediacy, how the function is called and by whom all become irrelevant if you check inside the function itself. It seems like a very strange way to partially guarantee something that can be guaranteed by checking at the time of the call. Maybe I need to see a more comprehensive example, but it seems odd to have a function that has a precondition which is not related to any argument, or state used by the function and which cannot be checked when it is passed to a separate lexical scope.

Also, I believe the issue with strictNullChecks and deferred execution is related to assumptions made about the nullity of a value between the time a closure using it is defined and invoked. That's not the case here.

tinganho

tinganho commented on Jun 19, 2016

@tinganho
ContributorAuthor

OK but then why not just check inside the function

I write a lot of functions and private methods that expects some referenced variables or properties to be not null. I can use the unwrap operator to unwrap them or just add an if statement in those cases. The latter paying a runtime cost and the former paying an unsafe cost.

I think declaring the function to expect some references to be not null is the most type safest and you also don't pay a runtime cost with one or more if checks. The function declarations are also more readable function ... requires something !== null.

An example case given below. Model can be null. bindModel method is declared below that this.model cannot be null when called.

class PostView() {
   model: Post | null;
   view() {
   }

   bind() {
     if (this.post) {
        this.bindModel();
     }
   }

   bindModel(): void
        requires this.model != null
   {
        this.model.on('change:title', () => alert('hello'))
   }
}
aluanhaddad

aluanhaddad commented on Jun 19, 2016

@aluanhaddad
Contributor

OK, I see what you are saying, but now that you can use strictNullChecks, parameterization as in

bindModel(model: Post): void {
    model.on('change:title', () => alert('hello'));
}

gives you what you want without adding additional syntactic complexity to the language. This also makes the function more reusable, readable, and explicit about its dependencies. It says requires model !== null by simply declaring a parameter.

tinganho

tinganho commented on Jun 19, 2016

@tinganho
ContributorAuthor

I haven't really liked parameterisation solutions. It can get quite ugly if you have a lot of parameters you need to define. Though I agree it solves the problem.

zpdDG4gta8XKpMCd

zpdDG4gta8XKpMCd commented on Jun 19, 2016

@zpdDG4gta8XKpMCd

how can you ensure anything via checks at runtime?

added
SuggestionAn idea for TypeScript
Too ComplexAn issue which adding support for may be too complex for the value it adds
on Aug 18, 2016
RyanCavanaugh

RyanCavanaugh commented on Aug 18, 2016

@RyanCavanaugh
Member

This seems way too complex for something that could really only represent a small fraction of the kind of "I need X side effects first" things you'd really need to make this a comprehensive solution.

locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    SuggestionAn idea for TypeScriptToo ComplexAn issue which adding support for may be too complex for the value it adds

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @zpdDG4gta8XKpMCd@tinganho@aluanhaddad@RyanCavanaugh

        Issue actions

          Guarded function declarations · Issue #9253 · microsoft/TypeScript