Украинское сообщество программистов

Closures (Re: функциональное программирование на Python)

motus
Опубликовано 21.01.2006 в Статьи

Началось все с того, что Макс привел такой пример замыкания в питоне:

>>> 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)

Просто глаз радуется!
Так что остальным языкам еще есть куда стремиться, имно. :-)

Теги: , , , , ,

1 звезда2 звезды3 звезды4 звезды5 звезд (Еще не оценили)
Загрузка ... Загрузка ...

Понравилась статья? Подпишись на обновления по RSS/E-mail

Подписаться, не оставляя комментарий

Все комментарии (7) к “Closures (Re: функциональное программирование на Python)” RSS

  1. Drobel

    Насчет остальных языков. Насколько я понимаю, в Java то же самое реализуется через анонимный класс и интерфейс?

  2. motus

    скорее нет, чем да. из анонимного класса все переменные вне его доступны только по чтению. более того, они все должны быть явно обозначены final.
    вот жалкая попытка функционального программирования на java:

    package test.closure;
    
    import java.util.Collection;
    import java.util.ArrayList;
    
    public class Test {
    
      public interface Functor<T, V> {
        public T apply( V value );
      }
    
      public static <T, V> void map(
          Functor<T, V> func, Collection<V> src, Collection<T> dst ) {
        for ( V elem : src ) {
          dst.add( func.apply(elem) );
        }
      }
    
      public static void main( final String[] args ) {
    
        ArrayList<String> list = new ArrayList<String>(args.length);
        ArrayList<String> res  = new ArrayList<String>(args.length);
    
        for ( String arg : args ) {
          list.add( arg );
        }
    
        map(
          new Functor<String, String>() {
            private int count = 0;
            public String apply( String value ) {
              return ++count + " of " + args.length + ": " + value;
            }
          }, list, res );
    
        System.out.println( res );
      }
    }
    

    если попытаться вынести counter за пределы вызова map() или убрать final перед args, ничего не скомпилится.

  3. Drobel

    Немного не так. Из анонимного класса доступны только для чтения не сами переменные, а ссылки на них. Т.е. проблема решается или как в примере с Python в приведенной статье или посредством работы только с объектами.

  4. motus

    да, верно. т.е. фактически нужно ручками пометить как final те ссылки, которые могут войти в замыкание.

  5. Alex Baran

    У Smalltalk-а с замыканиями порядок:

    Collection>>sum: initial
    | acc |
    acc := initial.
    self do: [:e | acc := acc + e].
    ^acc

    #(1 2 3 4 ) sum: 0 получаем 10
    #(1 2 3 4) sum: 100 получаем 110

    Это вообщем-то не фукциональный стиль(как и примеры выше на других языках). На Scheme в функциональном стиле могло бы выглядеть так:

    (foldl + 100 ‘(1 2 3 4)) вернет 110

    Вроде как нечестно использовать библиотечную функцию foldl, поэтому напишем свой fold:

    (define (myfold f initial list)
    (if (empty? list)
    initial
    (f
    initial
    (myfold f (car list) (cdr list)))))

    Получилось не намного больше реализации с set! и встроенной функцией “+”, зато мы получили возможность выполнять не только сложение:

    (myfold + 100 ‘(1 2 3 4)) вернет 110

    (myfold * 2 ‘(3 5)) вернет 30

    (myfold max 0 ‘(1 2 3 4)) вернет 4

  6. Anonymous

    У Smalltalk-а с замыканиями порядок:


    Collection>>sum: initial
    | acc |
    acc := initial.
    self do: [:e | acc := acc + e].
    ^acc


    #(1 2 3 4 ) sum: 0 получаем 10
    #(1 2 3 4) sum: 100 получаем 110

    Это вообщем-то не фукциональный стиль(как и примеры выше на других языках). На Scheme в функциональном стиле могло бы выглядеть так:


    (foldl + 100 '(1 2 3 4)) вернет 110

    Вроде как нечестно использовать библиотечную функцию foldl, поэтому напишем свой fold:


    (define (myfold f initial list)
    (if (empty? list)
    initial
    (f
    initial
    (myfold f (car list) (cdr list)))))

    Получилось не намного больше реализации с set! и встроенной функцией “+”, зато мы получили возможность выполнять не только сложение:


    (myfold + 100 '(1 2 3 4)) вернет 110

    (myfold * 2 '(3 5)) вернет 30

    (myfold max 0 '(1 2 3 4)) вернет 4

  7. Anonymous

    Какой тэг использовать для кода?

Оставить комментарий

Указать свой сайт могут только зарегистрированные пользователи. Регистрация или вход.

Архив

Добавить статью

Станьте автором нашего сайта!

Какие материалы подходят для публикации? — Такие.

Присылайте статьи на editors@developers.org.ua.

Подробнее.

Популярные теги

Все теги

Комментарии

Последние комментарии

интернет магазин бытовая техника магазин Laptoper