高阶函数:自动生成与手动定义

时间:2012-12-12 11:59:19

标签: python python-3.x functional-programming implicit-conversion higher-order-functions

我正在尝试延迟评估,因此我更愿意尽可能长时间地使用函数。

我有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(它应该约束向量中的每个单独元素)。我看到三个选项:

  1. 为矢量函数创建cap_if的新版本,例如vector_cap_if。但是,我需要为所有其他高阶函数执行此操作,这感觉不合适。不过,这种方法的优点是,我可以在将来用numpy函数替换这些函数的实现,以获得巨大的性能提升。

  2. 实现将函数类型从“数字 - >数字”“提升”到“<函数从数据到数字>到<函数从数据到数字>”和“数字”的函数 - >数字“to”<从数据向量到数字的函数>到<从向量数据到数字的函数>“。我们称这些函数为raise_to_data_functionraise_to_vector_function。然后我可以将basic_cap_if定义为单个数字的函数(而不是更高阶函数);我也为我需要的其他类似帮助函数做同样的事情。然后我使用raise_to_data_function(basic_cap_if)代替cap_ifraise_to_vector_function(basic_cap_if)代替cap_if_vector。这种方法似乎更优雅,因为我只需要实现一次基本功能。但是它失去了我上面描述的可能的性能提升,并且它还导致代码中有很多函数调用。

  3. 我可以按照2中的方法进行操作,但会根据需要在需要时自动应用raise_to_data_functionraise_to_vector_function个函数。据推测,我可以在__or__方法(函数应用程序)中实现它:如果它检测到传递给simple_cap_if的函数,它将检查正在传递的函数的签名,并应用适当的{{1功能到右侧。 (例如,可以通过使用raise_to的不同子类的不同签名成员的函数来公开签名;或者通过在Function中使用指定的方法来公开签名。这似乎是非常hacky,因为可能会发生许多隐式类型转换;但它确实减少了代码的混乱。

  4. 我是否错过了更好的方法,和/或针对这些方法的一些论据?

0 个答案:

没有答案