Skip to content

Bug in compiling lambda default argument values #36

Closed

Description

@elgow

Transcrypt has a bug in the way it compiles code for default values of arguments to lambda functions. The bug is serious, because the ability to close on several different values in callback functions for ajax calls is crucial for ajax programming.

Python closures capture variables, not their values. The hack in Python to create a closure that captures the value of a variable rather than the variable itself is like this:

for r in rows.indexes():
    row = rows.row(r)
    deferreds.append(JQ.post("assign_to_run", {'run': run, 'sample_data': JSON.stringify([row.data()[12]])}) \
         .done(lambda *, rw=row: rw.deselect() ))

but Transcrypt does not capture the value of row into rw. Instead it treats it as a closure over the variable row itself:

for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
    var r = __iter0__ [__index0__];
    var row = rows.row (r);
    deferreds.append ($.post ('assign_to_run', dict ({'run': run, 'sample_data': JSON.stringify (list ([row.data () [12]]))})).done ((function __lambda__ () {
        var rw = row;
        return rw.deselect ();})));
    }

When the above javascript code is run it will call deselect() on the last value assigned to row as many times as there were loop iterations, which isn't what it should be doing.

Activity

  1. elgow commented on Apr 15, 2016

    @elgow
    Author

    To make matters worse, Transcrypt moves evaluation of arguments into the function, which is wrong.

    lambda *, rw=capture(row) : rw.deselect()

    becomes when compiled:

    function __lambda__ () {
        var rw = capture (row);
        return rw.deselect ();})));
    }

    proper behavior is shown by this code

    def capture(v):
       val = v
       return v
    
    fs = []
    for i in [1,2,3]:
       fs.append(lambda *, ii=capture(i) : print(ii, i))
    
    for f in fs:
       f()

    which will print

    1 3
    2 3
    3 3

  2. JdeH commented on Apr 15, 2016

    @JdeH
    Collaborator

    Thanks for the thorough testing and analysis!
    I'll try to fix this in the next version.

  3. self-assigned this
    on Apr 15, 2016
  4. elgow commented on Apr 15, 2016

    @elgow
    Author

    Thank you for taking such quick action on this, and thanks also for Transcrypt. It's very nice to be able to write client-side code in Python instead of ES5.

  5. JdeH commented on Apr 15, 2016

    @JdeH
    Collaborator

    Well, to be honest, currently I am quite immersed in the source maps, so I'm afraid it will still be some time before the next version rolls out. I will try to do all the bug fixes in one go. Don't know if that succeeds.

  6. JdeH commented on Apr 21, 2016

    @JdeH
    Collaborator

    As a first step I've tried to construct a minimal example to experiment with this:

    results = []
    
    for i in range (10):
        # results.append (lambda: i)
        results.append (lambda j = i: j)
    
    print (results [0] ())
    

    which generates:

    var results = list ([]);
    for (var i = 0; i < 10; i++) {
        results.append ((function __lambda__ (j) {
            if (typeof j == 'undefined' || (j != null && j .__class__ == __kwargdict__)) {;
                var j = i;
            };
            return j;}));
    }
    print (results [0] ());
    
  7. JdeH commented on Apr 21, 2016

    @JdeH
    Collaborator

    Unfortunately I currently don't know a good and compact mechanism to make this work properly.
    A workaround is to create a separate inner scope for your lambda:

    results = []
    
    for i in range (10):
        # results.append (lambda: i)                          # Will never work
        # results.append (lambda j = i: j)                   # Works in Python
        results.append ((lambda j: lambda: j) (i))      # Works in Transcrypt and Python
    
    print (results [3] ())
    

    I'll add this to the docs. For now consider the necessity of this workaround a limitation of Transcrypt. I've added the workaround as a testcase to the autotest including the comments, to document it for future moments of brightness...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions