Связывание выражений
Недовольный полурешениями, один из читателей - Ричарда Дейвис (Richard Davies) - поднял вопрос, можем ли мы целиком переместить связывания в отдельные выражения. Давайте попытаемся понять, зачем нам может этого захотеться, а также продемонстрируем замечательно элегантный способ этого добиться, предоставленный участником comp.lang.python.
Давайте сначала вспомним о классе Bindings, определенном в модуле functional. Используя свойства этого класса, мы смогли гарантировать, что отдельное имя имеет единственное значение в пределах области данного блока:
#------- Python FP session with guarded rebinding -------#
>>> from functional import *
>>> let = Bindings()
>>> let.car = lambda lst: lst[0]
>>> let.car = lambda lst: lst[2]
Traceback (innermost last):
File "", line 1, in ?
File "d:\tools\functional.py", line 976, in __setattr__
raise BindingError, "Binding '%s' cannot be modified." % name
functional.BindingError: Binding 'car' cannot be modified.
>>> let.car(range(10))
0
С помощью класса Bindings нам удалось достичь желаемого результата в пределах модуля или функции, но в отношении отдельного выражения мы бессильны. Тем не менее, для семейства ML-языков вполне естественно создавать связывания в пределах отдельного выражения:
#-------- Haskell expression-level name bindings --------#
-- car (x:xs) = x -- *could* create module-level binding
list_of_list = [[1,2,3],[4,5,6],[7,8,9]]
-- 'where' clause for expression-level binding
firsts1 = [car x | x <- list_of_list] where car (x:xs) = x
-- 'let' clause for expression-level binding
firsts2 = let car (x:xs) = x in [car x | x <- list_of_list]
-- more idiomatic higher-order 'map' technique
firsts3 = map car list_of_list where car (x:xs) = x
-- Result: firsts1 == firsts2 == firsts3 == [1,4,7]
Грэг Эвинг (Greg Ewing) заметил, что мы можем достичь того же эффекта, воспользовавшись списочными встраиваниями Python (list comprehensions); мы даже можем сделать это почти столь же ясным способом, как в Haskell:
#------ Python 2.0+ expression-level name bindings ------#
>>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]]
>>> [car_x for x in list_of_list for car_x in (x[0],)]
[1, 4, 7]
Этот прием - размещение выражения внутри одноэлементного кортежа в списочном встраивании - не позволяет использовать связывание на уровне выражений с функциями высшего порядка. Для использования таких функций мы все еще должны использовать область блока:
#------- Python block-level bindings with 'map()' -------#
>>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]]
>>> let = Bindings()
>>> let.car = lambda l: l[0]
>>> map(let.car,list_of_list)
[1, 4, 7]
Неплохо, хотя если мы хотим использовать map(), область связывания остается несколько шире, чем мы того хотели. Тем не менее, можно уговорить списочное встраивание делать для нас связывание имен, даже если список - не то, что нам нужно в конечном счете:
#---- "Stepping down" from Python list-comprehension ----#
# Compare Haskell expression:
# result = func car_car
# where
# car (x:xs) = x
# car_car = car (car list_of_list)
# func x = x + x^2
>>> [func for x in list_of_list
... for car in (x[0],)
... for func in (car+car**2,)][0]
2
В этом примере мы произвели арифметическое действие над первым элементом первого элемента списка list_of_list
и одновременно поименовали это действие (но только в области объемлющего выражения). В качестве "оптимизации" можно посоветовать создавать список длиной не более одного элемента, поскольку с помощью индекса [0] в конце выражения выбираем только первый элемент:
#---- Efficient stepping down from list-comprehension ---#
>>> [func for x in list_of_list[:1]
... for car in (x[0],)
... for func in (car+car**2,)][0] 2