Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Closure semantics #500

Closed
nddrylliog opened this Issue · 5 comments

2 participants

@nddrylliog
Collaborator

This is loads of fun:

main: func {

  a := 42 

  plusA := func (b: Int) -> Int {
    a + b
  }

  "%d" printfln(plusA(2))
  a = 23 
  "%d" printfln(plusA(2))

}

Prints 44 twice.

However,

main: func {

  a := 42 

  plusA := func (b: Int) -> Int {
    c := a
    a = c
    a + b
  }

  "%d" printfln(plusA(2))
  a = 23 
  "%d" printfln(plusA(2))

}

Prints 44, then 25.

This is because vars are only passed by-ref if we modify them. More info in the upcoming comments, this is already long.

@nddrylliog
Collaborator

So here's the (commented) code of main, for the first version:

lang_Numbers__Int main() {
    GC_INIT();
    closure_load();

    /* Declare & initialize a */
    lang_Numbers__SSizeT a = 42;

    /* Allocate room for closure */
    __closure_closure222_ctx* __ctx223 = lang_Memory__gc_malloc(((lang_types__Class*)__closure_closure222_ctx_class())->size);

    /* Capture context */
    (*(__ctx223)) = (__closure_closure222_ctx) { 
        a    
    };   

    /* Set up closure struct & put it in var 'plusA'. Note the unnecessary temp variable.
       It doesn't hurt much, though, the C compiler will optimize it out. */
    lang_types__Closure __closure224 = (lang_types__Closure) { 
        closure____closure_closure222_thunk, 
        __ctx223
    };   
    lang_types__Closure plusA = __closure224;

    /* Varargs for first println */
    struct { 
        lang_types__Pointer __f1;
        lang_Numbers__Int __f2;
    } ____va_args114;
    lang_VarArgs__VarArgs ____va115 = (lang_VarArgs__VarArgs) { 
        (void*) &(____va_args114), 
        NULL, 
        1    
    };   
    lang_String__String_printfln((lang_String__String*) __strLit1, (____va_args114.__f1 = lang_Numbers__Int_class(), ____va_args114.__f2 = ((lang_Numbers__Int (*)(lang_Numbers__Int, void*)) plusA.thunk)(2, plusA.context), ____va115));

    /* Modify a, but not the closure's context */
    a = 23;

    /* Varargs for second println */
    struct { 
        lang_types__Pointer __f1;
        lang_Numbers__Int __f2;    } ____va_args116;
    lang_VarArgs__VarArgs ____va117 = (lang_VarArgs__VarArgs) {         (void*) &(____va_args116),         NULL, 
        1        };   
    lang_String__String_printfln((lang_String__String*) __strLit2, (____va_args116.__f1 = lang_Numbers__Int_class(), ____va_args116.__f2 = ((lang_Numbers__Int (*)(lang_Numbers__Int, void*)) plusA.thunk)(2, plusA.context), ____va117));
    return ((lang_Numbers__Int) (0));}

Along with the code of the closure and its thunk:

lang_Numbers__Int closure____closure_closure222(lang_Numbers__SSizeT a, lang_Numbers__Int b) {     return ((lang_Numbers__Int) (a + b)); 
}

lang_Numbers__Int closure____closure_closure222_thunk(lang_Numbers__Int b, __closure_closure222_ctx* __context__) {
    return closure____closure_closure222((*__context__).a, b);
}

As you can see, the context is captured when declaring the closure, so our modification of a doesn't mean shit.

Whereas when we modify a inside the closure, it's passed by ref, so everything works.

The questions are:

  • Do we really want that behaviour
  • If we do want it, should we also allow a way to force a to be passed byref even if we don't modify it inside the closure
  • For things that are passed byref, we should add a check so that the closure isn't retained after the variables it references go out of scope.
@shamanas
Collaborator

I think the sample you provided should have this behavior.
However, forcing rock to pass a variable byref sounds like a good idea.
I don't really know about the syntax though... Maybe something like:

a := 42

plusA := func [&a] (b: Int) -> Int {
    a + b
}

// etc...

// For anonymous functions:
[ &a | x ] a + x

Also, yes, the check sounds good but I don't have a clue on how to implement it at the moment...

@shamanas
Collaborator

@nddrylliog
What about somthing like:

a := byref 42

plusA := func (b: Int) -> Int {
    a + b
}

//etc...

that would cause a to be passed by reference to all closures?
Obviously, it's not really flexible but it solves the need for new, awkward syntax.

@nddrylliog
Collaborator

Ugh, not another keyword :( Plus the semantics are unclear.. no, in reality there are many ways to work around the problem in the original post, it's just a bit surprising at first but once you realize how closures work it's okay.

@nddrylliog
Collaborator

So, seems my answer was "it's okay", there's no point in keeping this open.

For the reference, if one wants to force by-ref one can always do something like that:

a := 42

plusA := func (b: Int) -> Int {
    a&
    a + b
}

"#{plusA(2)}" println()
a = 0
"#{plusA(2)}" println()

See? Just as good as an annotation, only no special syntax :)

@nddrylliog nddrylliog closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.