Closures (Re: функциональное программирование на Python)
Началось все с того, что Макс привел такой пример замыкания в питоне:
>>> def shamba(n): ... def f(): ... print n ... return f ... >>> g = shamba(42) >>> g() 42Все хорошо до тех пор, пока мы не пытаемся менять значения переменных внутри замыкания:
>>> def outer(x): ... acc = 0 ... def inner(y): ... acc += y ... return acc ... map(inner, x) ... return acc ... >>> outer([1,2,3,4]) Traceback (most recent call last): File "", line 1, in ? File "", line 6, in outer File "", line 4, in inner UnboundLocalError: local variable 'acc' referenced before assignmentПроблема в том, что если слева от оператора ’=’ находится идентификатор, питон выполняет одновременно и присваивание, и связывание — в локальном контексте. Грабли обходятся достаточно просто — нужно, чтобы в левой части был не идентификатор, а ссылка на объект, который можно поменять — например, элемент внутри контейнера:
>>> def outer(x): ... acc = [0] ... def inner(y): ... acc[0] += y ... return acc[0] ... map(inner, x) ... return acc[0] ... >>> outer([1,2,3,4]) 10Не очень-то красиво, но работает.
После подобных упражнений мне стало любопытно, как такие трюки проходят в других языках. В ruby вылезли те же проблемы:
acc = 10000 def outer(arr) acc = 0 def inner(x) acc += x end arr.each(&method(:inner)) acc end p outer([1,2,3,4])При вызове outer() валится с ошибкой:
NoMethodError: undefined method `+' for nil:NilClass
Попытки использовать ссылку на элемент массива, как в питоне, не помогают. Напрашивается неутешительный вывод о том, что внутренние функции в ruby не являются честными замыканиями (или это я чего-то не понимаю?)
Справедливости ради надо сказать, что с блоками в ruby дело обстоит немного лучше:
acc = 10000 # Not visible from inside the block def outer(x) acc = 0 # Block fails if we comment out this line x.each { |x| acc += x } acc end p outer([1,2,3,4]), accпечатает, как и положено,
10 10000Обратите внимание на комментарии в коде — блок связывается только с локальным контекстом, т.е. поиска вверх не происходит.
Теперь perl. Тут неподготовленного меня сразу поджидали грабли с именоваными sub-ами:
#!/usr/bin/perl -w use strict; my $acc = 10000; sub outer { my $acc = shift; sub inner { $acc += $_; return $_; } map( &inner, @_ ); return $acc; } print "It kinda works: ", outer(0,1,2,3,4), " $accn"; print "But not quite: ", outer(100,1,2,3,4), " $accn";Для именованого sub-a cвязывание происходит раз, при первом вызове outer(), т.е. программа напечатает такое:
Variable "$acc" will not stay shared at ./closure3.pl line 10. It kinda works: 10 10000 But not quite: 100 10000Вот вам и польза от -w. Без этого ключика все работает молча и неправильно. Брр.
perldoc, разумеется, утверждает, что такое поведение не глюк, а фича, и предлагает использовать анонимные sub-ы:
#!/usr/bin/perl -w use strict; my $acc = 10000; sub outer { my $acc = shift; my $inner = sub { $acc += $_; return $_; }; map( &$inner, @_ ); return $acc; } print outer(0,1,2,3,4), " $accn"; print outer(100,1,2,3,4), " $accn";тут все работает просто замечательно:
10 10000 110 10000Вывод — идея my() в prel-e очень даже помогает жить. Мне даже кажется, что питону и ruby не хватает чего-то подобного. В конце концов, my() - это почти как let в scheme. И, кстати, надо бы на досуге поиграть с замыканиями в JavaScript-e — наличие var там сулит определенные надежды. Ну, и раз я уже упомянул scheme, вот вам напоследок пример совсем правильного замыкания — для полноты картины:
(define acc 10000) (define (outer start x) (let ((acc start)) (define (inner y) (set! acc (+ acc y)) ) (map inner x) acc ) ) (write (list (outer 0 '(1 2 3 4)) (outer 100 '(1 2 3 4)) acc )) (newline)печатает
(10 110 10000)Просто глаз радуется!
Так что остальным языкам еще есть куда стремиться, имно. :-)
Все про українське ІТ в телеграмі — підписуйтеся на канал DOU
5 коментарів
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.