python function-composition

¿Es una buena idea tener un azúcar de sintaxis para funcionar en Python?



function-composition (4)

Hace algún tiempo revisé los documentos de Haskell y descubrí que su operador de composición funcional es realmente agradable. Así que he implementado este pequeño decorador:

from functools import partial class _compfunc(partial): def __lshift__(self, y): f = lambda *args, **kwargs: self.func(y(*args, **kwargs)) return _compfunc(f) def __rshift__(self, y): f = lambda *args, **kwargs: y(self.func(*args, **kwargs)) return _compfunc(f) def composable(f): return _compfunc(f) @composable def f1(x): return x * 2 @composable def f2(x): return x + 3 @composable def f3(x): return (-1) * x @composable def f4(a): return a + [0] print (f1 >> f2 >> f3)(3) #-9 print (f4 >> f1)([1, 2]) #[1, 2, 0, 1, 2, 0] print (f4 << f1)([1, 2]) #[1, 2, 1, 2, 0]

El problema: sin soporte de idioma, no podemos usar esta sintaxis en funciones integradas o lambdas como esta:

((lambda x: x + 3) >> abs)(2)

La pregunta: ¿es útil? ¿Vale la pena ser discutido en la lista de correo de python?


En mi humilde opinión: no, no lo es. Aunque me gusta Haskell, esto parece no encajar en Python. En lugar de (f1 >> f2 >> f3) puede compose(f1, f2, f3) y eso resuelve su problema: puede usarlo con cualquier llamador sin sobrecargar, decorar o cambiar el núcleo (IIRC alguien ya ha propuesto) functools.compose al menos una vez; no puedo encontrarlo en este momento).

Además, la definición del idioma está congelada en este momento, por lo que probablemente rechacen ese tipo de cambio de todos modos, consulte PEP 3003 .


La composición de funciones no es una operación súper común en Python, especialmente de una manera que claramente no se necesita un operador de composición. Si se agregó algo, no estoy seguro de que me guste la opción de << y >> para Python, que no son tan obvias para mí como parecen serlo para usted.

Sospecho que muchas personas se sentirían más cómodas con una función de compose , cuyo orden no es problemático: compose(f, g)(x) significaría f(g(x)) , el mismo orden que o en matemáticas y . en Haskell. Python intenta evitar el uso de la puntuación cuando las palabras en inglés sirven, especialmente cuando los caracteres especiales no tienen un significado ampliamente conocido. (Se hacen excepciones para cosas que parecen demasiado útiles como para dejarlas pasar, como @ para decoradores (con muchas dudas) y * y ** para argumentos de función).

Si elige enviar esto a Python-Ideas, probablemente ganará mucha más gente si puede encontrar algunos ejemplos en la biblioteca estándar o en las bibliotecas populares de Python que la composición de la función podría haber hecho el código más claro, fácil de escribir, mantener, o eficiente


No tengo suficiente experiencia con Python para ver si un cambio de idioma valdría la pena. Pero quería describir las opciones disponibles con el idioma actual.

Para evitar la creación de comportamientos inesperados, la composición funcional debería seguir idealmente el orden de operaciones matemático estándar (o Haskell), es decir, f ∘ g ∘ h debe significar aplicar h , luego g , luego f .

Si desea utilizar un operador existente en Python, diga << , ya que menciona que tendría un problema con las lambdas y los componentes integrados. Puede hacer su vida más fácil definiendo la versión reflejada __rlshift__ además de __lshift__ . Con eso, lambda / incorporados adyacentes a objetos composable serían atendidos. Cuando tenga dos lambda / incorporados adyacentes, deberá convertirlos explícitamente (solo uno de ellos) con composable , como sugiere @ si14. Tenga en cuenta que realmente quiero decir __rlshift__ , no __rshift__ ; de hecho, no recomendaría el uso de __rshift__ , ya que el cambio de orden es confuso a pesar de la sugerencia direccional proporcionada por la forma del operador.

Pero hay otro enfoque que tal vez quiera considerar. Ferdinand Jamitzky tiene una excelente receta para definir los operadores de pseudo infijo en Python que funcionan incluso en los incorporados. Con esto, puedes escribir f |o| g f |o| g para la composición de la función, que en realidad se ve muy razonable.


Puede hacerlo con reduce , aunque el orden de las llamadas es solo de izquierda a derecha:

def f1(a): return a+1 def f2(a): return a+10 def f3(a): return a+100 def call(a,f): return f(a) reduce(call, (f1, f2, f3), 5) # 5 -> f1 -> f2 -> f3 -> 116 reduce(call, ((lambda x: x+3), abs), 2) # 5