функциональное программирование на Python
Макс ИщенкоОпубликовано 19.01.2006 в Разработка, статьи
Несмотря на изначально убогую форму ламбда-функций а затем и deprecation типичных функционалов map, filter, reduce функциональное программирование на Python развивается и довольно успешно. Может быть потому, что функции с самого начала были first-class citizens.
Про list comprehensions уже наверное все слышали, как и про нормальную поддержку замыканий (closures). Сюда же можно отнести и декораторы, появившиеся в 2.4 и являющиеся, по сути, средством создания замыканий вокруг объектов-функций.
Библиотечные модули тоже развиваются, в Python 2.3 появился itertools (я им, правда никогда не пользовался), а в 2.5 обещают partial function application. Выглядеть это будет так:
def log (level, message, subsystem):
print '%s: %s' % (subsystem, message, level)
...
server_log = partial(log, subsystem='server')
debug = partial(log, DEBUG)
server_log(INFO, 'test message')
debug('another test', 'ui')
Функция partial, кстати, получила отдельный библиотечный модуль - functional, так что далі буде.
P.S.: Конечно, partial имеет мало общего (за исключением внешнего сходства) с function currying из ФП, но все равно забавно. Надо бы еще о generic dispatch написать - это даже более интересно.
Понравилась статья? Подпишись на обновления по RSS/E-mail



ну, насчет нормальной поддержки замыканий ты погорячился, мне кажется.
Почему? В моем представлении closure == функция + контекст. Ну так оно есть:
>>> 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
>>>
Yuck.
Ну, тут да, есть момент. Хотя я всегда рассматривал вложенный контент как read-only.
ну, так и анонимные классы в Java можно замыканиями обозвать
имно корень зла в том, как создаются новые переменные в питоне.
в ruby, кстати, те же проблемы:
closure3.rb(main):001:0> acc = 10000
=> 10000
closure3.rb(main):002:0> def outer(arr)
closure3.rb(main):003:1> acc = 0
closure3.rb(main):004:1> def inner(x)
closure3.rb(main):005:2> acc += x
closure3.rb(main):006:2> end
closure3.rb(main):007:1> arr.each(&method(:inner))
closure3.rb(main):008:1> acc
closure3.rb(main):009:1> end
=> nil
closure3.rb(main):010:0> p outer([1,2,3,4])
NoMethodError: undefined method `+’ for nil:NilClass
from closure3.rb:5:in `inner’
from closure3.rb:10:in `to_proc’
from closure3.rb:7:in `outer’
from closure3.rb:10
from :0
closure3.rb(main):011:0>
только, в отличие от питона, в ruby это, похоже, вообще не лечится, т.е. вложенные функции замыканиями не являются. в то же время, блоки в ruby ведут себя почти правильно:
closure4.rb(main):001:0> acc = 10000
=> 10000
closure4.rb(main):002:0> def outer(x)
closure4.rb(main):003:1> acc = 0 # Fails if we comment this line out
closure4.rb(main):004:1> x.each { |x| acc += x }
closure4.rb(main):005:1> acc
closure4.rb(main):006:1> end
=> nil
closure4.rb(main):007:0> p outer([1,2,3,4]), acc
10
10000
=> nil
такие дела.
вдогонку - попробовал то же самое на перле. анонимные sub-ы, похоже, работают правильно, а вот именованые sub-ы ведут себя довольно коварно, особенно если не использовать ключик -w:
motus@smatusev-1:~/work/svn/misc$ cat closure3.pl
#!/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), " $acc\n";
print "But not quite: ", outer(100,1,2,3,4), " $acc\n";
motus@smatusev-1:~/work/svn/misc$ ./closure3.pl

Variable "$acc" will not stay shared at ./closure3.pl line 10.
It kinda works: 10 10000
But not quite: 100 10000
страшновато
а вот такое работает на ура:
motus@smatusev-1:~/work/svn/misc$ cat closure4.pl
#!/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), " $acc\n";
print outer(100,1,2,3,4), " $acc\n";
motus@smatusev-1:~/work/svn/misc$ ./closure4.pl
10 10000
110 10000
вывод - идея my() в перле помогает жить. мне даже кажется, что питону и руби не хватает чего-то подобного. в конце концов, my() это почти как let в scheme.
ну и раз я уже заговорил о scheme - вот пример совсем правильного замыкания:
motus@smatusev-1:~/work/svn/misc$ cat closure.scm
(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)
motus@smatusev-1:~/work/svn/misc$ guile -s closure.scm

(10 110 10000)
так что остальным языкам есть куда расти
Подожди, а какой-нибудь real-world пример ты можешь привести? А то я как-то не соображу. А так - любой mutable type справляется:
class counter:
def update(n):
..
acc = counter()
я не говорю, что замыканий в питоне нет - они есть, но поскольку оператор ‘=’ в питоне одновременно производит и связывание, и присваивание, иногда приходится извращаться, т.к. питон не знает, объявляем ли мы новую переменную или изменяем значение старой.
твой mutable type это по сути такой же трюк, как и мой пример с массивом (acc = [0]). т.е. проблема не с замыканиями как таковыми, а с тем, как они вписываются в общую картину.