I Challenge You to Debug These 7 Lines of Code Under 9 Minutes
theodo.fr
theodo.fr
def foo(a, methods=[]): # methods defaults to [] if not specified
ohno.1. mutating the `methods` parameter directly, I don't even care if it works, is a bad idea
2. naming the thing methods and method might trip something up
By just changing the code so that we declare the local methods array (name it methodsX or something else) to be empty, this fixed the first 3 problems (where the function is empty). I don't know enough of Python syntax to make it accept the second parameter being a array of functions.
I know this doesn't work (because python assignment rules)
def foo(a, methods=[]):
m=[];
for bla in methods:
m.append(bla)
for i in range(3):
k = i
m.append(lambda x: x + k)
But this should work, according to quick'n'dirty google (because default assignment), and yet it doesn't. for i in range(3):
def bar(c, d=i): return c+d
m.append(lambda x: bar(x))
EDIT:
Ok, I'm something missing here.This doesn't capture the value of i
for i in range(3):
m.append(lambda x,i=i: x+i)
But this does. WTH?! for i in range(3):
m.append(lambda i=i: i) for i in range(3):
m.append(lambda x,i=i: x+i)
actually works, I just had a brain fart. :)If you want to talk about statistical significance, don't say "not completely". It either is or it isn't. If you think the p-value is important (and I'm not saying it is, but don't bother mentioning it if you don't think it is), then you can't claim a 35% speedup. If you're reporting results in the context of significance, and you decided that your results were not significant, then the 35% speedup is meaningless.
Haha racket can here is how.
#lang racket
(define (foo a [methods empty])
(let ([methods
(append methods
(for/list ([i (range 3)])
(lambda (x) (+ x i))))])
(for/sum ([m methods])
(m a))))
(foo 0) ;; Should be 3
(foo 1) ;; Should be 6
(foo 2) ;; Should be 9
(foo 2 (list (lambda (x) x))) ;; Should be 11
(foo 0)
(foo 1)
(foo 2)
;; Now the wrong way
;; The lambda capturing wrong
(define (foo a [methods empty])
(define i 0)
(let ([methods
(append methods
(for/list ([z (range 3)])
;; modifying the variable i
(set! i z)
(lambda (x) (+ x i))))])
(for/sum ([m methods])
(m a))))
;; this one doesn't do the default parameter wrong, because
;; methods and methods-default share an imutable list and we
;; modify only methods so beside "empty" they won't share the same
;; reference anymore, to get it wrong we should use a mutable list
;; (doesn't exist in racket).
(define foo
(let ([methods-default empty])
(lambda (a [methods methods-default])
;; (printf "~a ~a\n" (length methods) (length methods-default))
(define i 0)
(for ([z (range 3)])
(set! i z)
(set! methods (cons (lambda (x) (+ x i))
methods)))
(for/sum ([m (append methods methods-default)])
(m a)))))
;; So i use a box to make it mutable.
(define foo
(let ([methods-default (box empty)])
(lambda (a [methods methods-default])
;; (printf "~a ~a\n" (length (unbox methods)) (length (unbox methods-default)))
(define i 0)
(for ([z (range 3)])
(set! i z)
;; methods and methods-default share the same reference of box.
(set-box! methods (cons (lambda (x) (+ x i))
(unbox methods))))
(for/sum ([m (unbox methods)])
(m a)))))
The moral of the story, language design matter, immutable by default is good, lexical scoping is good, binding over variable is good, ruby, python, php and javascript (the languages the author mention) are bad in that regards.One thing php, ruby, python (but not javascript) has good over racket their identatation tend to be flat and racket (let form, no return statement) tend to go all the way to the right.
With python (and many other languages) I find myself getting bitten by behaviour like this every now and then. I use scheme for almost all my personal projects, and I can only remember getting bitten twice. Once was unexpected, and the other was laziness on my part.
In the method=[] part, the error occurs because python creates a single list, and passes that list by reference (as all lists are passed) into the function.
In the lambda part, the error occurs because python captures the variable from the enclosing scope by reference, not by value (as normal functions do with globals).
Or is this pattern actually used? It seems rather perl5-y.
However the default parameter issue looks like a python specific issue which is really strangely designed. I would normally say [] is a literal for an empty list which creates a new instance of an empty list every time it's used. So when it's used as a default parameter a fresh empty list should be created all times.
Is [] is global reference to a single mutable list which is only initially empty? That would make it pretty useless, since everyone could mutate it. If it's a single reference then that should at least be an immutable list which throws some exception when someone messes around with it.
No, the problem is default arguments to methods are evaluated once, when the method is created and that value is used for all calls. I don't know why that is, its definitely strange - any other language would evaluate the default args each time a function is called just like the regular args.
What languages are you thinking of where default values set to a references are set to a copy of that reference as it was at definition time at every evaluation?
Example:
scala> var x = 5
x: Int = 5
scala> def func(arg1: Int = (x+1)) = { println(arg1)}
func: (arg1: Int)Unit
scala> func()
6
scala> var x = 7
x: Int = 7
scala> func()
6 //unchanged
For reference vars the closure captures a local copy: scala> var list = List(1,2,3)
list: List[Int] = List(1, 2, 3)
scala> def printList(arg1: List[Int] = lis) = { println(arg1) }
printList: (arg1: List[Int])Unit
scala> printList()
List(1, 2, 3)
scala> var list = List(4,5,6)
list: List[Int] = List(4, 5, 6)
scala> printList()
List(1, 2, 3)Not to plug my 2 favorites that do (Ruby and Elixir/Erlang) or anything...
What really frustrates me with go is that it doesn't have a mature debugger so I have to resort to inserting print statements everywhere.
Not really. You have to explicitly specify you want capture-by-reference with C++ lambdas.
The code would also look very out-of-place to a C++ programmer, because you would never capture a loop counter by reference if you intend to use the lambda outside of the loop. Lifetimes don't just get magically extended into completely unrelated scopes.