Я пытаюсь немного отложить оценку, поэтому предпочитаю работать с функциями как можно дольше.
У меня есть class Function
, который определяет композицию и поточечную арифметику для функций:
from functools import reduce
def compose(*funcs):
'''
Compose a group of functions f1, f2, f3, ... into (f1(f2(f3(...))))
'''
result = reduce(lambda f, g: lambda *args, **kaargs: f(g(*args, **kaargs)), funcs))
return Function(result)
class Function:
'''
>>> f = Function(lambda x : x**2)
>>> g = Function(lambda x : x + 4)
>>> h = f/g
>>> h(6)
3.6
>>> (f + 1)(5)
26
>>> (2 * f)(3)
18
# >> means composition, but in the order opposite to the mathematical composition
>>> (f >> g)(6) # g(f(6))
40
# | means apply function: x | f is the same as f(x)
>>> 6 | f | g # g(f(6))
40
'''
# implicit type conversion from a non-callable arg to a function that returns arg
def __init__(self, arg):
if isinstance(arg, Function):
# would work without this special case, but I thought long chains
# of nested functions are best avoided for performance reasons (??)
self._func = arg._func
elif callable(arg):
self._func = arg
else:
self._func = lambda *args, **kwargs : arg
def __call__(self, *args, **kwargs):
return self._func(*args, **kwargs)
def __add__(lhs, rhs):
# implicit type conversions, to allow expressions like f + 1
lhs = Function(lhs)
rhs = Function(rhs)
new_f = lambda *args, **kwargs: lhs(*args, **kwargs) + rhs(*args, **kwargs)
return Function(new_f)
# same for __sub__, __mul__, __truediv__, and their reflected versions
# ...
# function composition
# similar to Haskell's ., but with reversed order
def __rshift__(lhs, rhs):
return compose(rhs, lhs)
def __rrshift__(rhs, lhs):
return compose(rhs, lhs)
# function application
# similar to Haskell's $, but with reversed order and left-associative
def __or__(lhs, rhs):
return rhs(lhs)
def __ror__(rhs, lhs):
return rhs(lhs)
Изначально все мои функции имели одинаковую сигнатуру: они принимали в качестве единственного аргумента экземпляр class Data
и возвращали float
. Тем не менее, моя реализация Function
не зависела от этой подписи.
Затем я начал добавлять различные функции более высокого порядка. Например, мне часто нужно создать ограниченную версию существующей функции, поэтому я написал функцию cap_if
:
from operator import le
def cap_if(func, rhs):
'''
Input arguments:
func: function that determines if constraint is violated
rhs: Function(rhs) is the function to use if constraint is violated
Output:
function that
takes as an argument function f, and
returns a function with the same signature as f
>>> f = Function(lambda x : x * 2)
>>> 5 | (f | cap_if(le, 15))
15
>>> 10 | (f | cap_if(le, 15))
20
>>> 5 | (f | cap_if(le, lambda x : x ** 2))
25
>>> 1.5 | (f | cap_if(le, lambda x : x ** 2))
3.0
'''
def transformation(original_f):
def transformed_f(*args, **kwargs):
lhs_value = original_f(*args, **kwargs)
rhs_value = rhs(*args, **kwargs) if callable(rhs) else rhs
if func(lhs_value, rhs_value):
return rhs_value
else:
return lhs_value
return Function(transformed_f)
return Function(transformation)
Вот в чем проблема. Теперь я хочу представить функции, которые принимают "вектор" из Data
экземпляров и возвращают "вектор" из чисел. На первый взгляд, я мог бы оставить свой существующий фреймворк без изменений. В конце концов, если я реализую вектор как, скажем, numpy.array
, векторы будут поддерживать поточечную арифметику, и поэтому поточечная арифметика в функциях будет работать, как задумано, без каких-либо изменений в приведенном выше коде.
Но приведенный выше код не работает с функциями более высокого порядка, такими как cap_if
(которые должны ограничивать каждый отдельный элемент в векторе). Я вижу три варианта:
Создайте новую версию
cap_if
, скажем,vector_cap_if
, для функции векторов. Но тогда мне нужно было бы сделать это для всех других функций более высокого порядка, что кажется нежелательным. Однако преимущество такого подхода заключается в том, что в будущем я мог бы заменить реализацию этих функций, скажем, наnumpy
функций для значительного прироста производительности.Реализовать функции, которые «повышают» тип функции с «число -> число» на «‹функция из данных в число> в ‹функция из данных в число>» и с «число -> число» в «‹функция из вектор данных в число> в ‹функция из вектора данных в число>". Назовем эти функции
raise_to_data_function
иraise_to_vector_function
. Затем я могу определитьbasic_cap_if
как функцию отдельных чисел (а не функцию более высокого порядка); Я делаю то же самое для других подобных вспомогательных функций, которые мне нужны. Затем я используюraise_to_data_function(basic_cap_if)
вместоcap_if
иraise_to_vector_function(basic_cap_if)
вместоcap_if_vector
. Этот подход кажется более элегантным в том смысле, что мне нужно реализовать каждую базовую функцию только один раз. Но при этом теряется возможный прирост производительности, описанный выше, а также получается код с большим количеством вызовов функций.Я мог бы следовать подходу в 2, но автоматически применять функции
raise_to_data_function
,raise_to_vector_function
всякий раз, когда это необходимо, в зависимости от контекста. Предположительно, я могу реализовать это внутри метода__or__
(приложение функции): если он обнаружит, что функция передаетсяsimple_cap_if
, он проверит подпись передаваемой функции и применит соответствующую функциюraise_to
к правой стороне. (Сигнатуры могут быть раскрыты, например, путем создания функций с разными сигнатурами членами разных подклассовFunction
или с помощью назначенного метода вFunction
). Это может показаться очень хакерским, поскольку может произойти много неявных преобразований типов; но это уменьшает беспорядок кода.
Мне не хватает лучшего подхода и/или некоторых аргументов за/против этих?
fmap
(в Haskell); Вы рассматривали возможность использования Functor? - person paul   schedule 12.12.2012Functor
, но попытаюсь выяснить. Я полагаю, что с таким подходом ничего нельзя сделать, чтобы лишить себя основного прироста производительности (я не смогу использовать специальные функцииnumpy
, которые обрабатывают весь вектор сразу)? - person max   schedule 13.12.2012rhs = Function(lhs)
в вашей функции__add__
, вы хотели поставитьrhs = Function(rhs)
? - person paul   schedule 13.12.2012