2010-02-17 7 views
18

Jakiś czas temu przejrzałem dokumentację Haskella i stwierdziłem, że jest to naprawdę funkcjonalny operator kompozycji. Tak już wdrożyły ten drobny dekorator:Czy dobrze jest mieć cukier składniowy do funkcji składu w Pythonie?

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] 

Problem: bez wsparcia językowego nie możemy korzystać z tej składni na wbudowanych funkcji lub lambdas tak:

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

Pytanie: jest to przydatne? Czy warto go omawiać na liście Pythona?

+0

... ale nadal można napisać coś takiego: print (składany (lambda x: x + 3) >> composable (abs)) (- 4) – si14

Odpowiedz

8

IMHO: nie, nie jest. Chociaż lubię Haskella, to po prostu nie pasuje do Pythona. Zamiast (f1 >> f2 >> f3) możesz zrobić compose(f1, f2, f3) i to rozwiązuje twój problem - możesz go użyć z dowolnym wywoływaczem bez przeciążania, dekorowania lub zmieniania rdzenia (IIRC ktoś już zaproponował functools.compose co najmniej raz, nie mogę go teraz znaleźć).

Poza tym definicja języka jest teraz zamrożona, więc i tak prawdopodobnie odrzuci tego rodzaju zmianę - patrz PEP 3003.

+0

compose() nie jest tak jasne. Jaki jest kierunek kompozycji? Musisz przejrzeć dokumentację, aby odpowiedzieć na to pytanie. – si14

+2

@ si14: Nie znając Haskella, nie znajduję >> i << tego wyczyść (czy to znaczy wstawić lub zawinąć inną funkcję?). – nikow

+1

@Nikow: Ja też tego nie wiem, ale >> i << wyraźnie definiują "przepływ obliczeniowy": f1 >> f2 >> f3 oznacza "put result od f1 do f2, potem wynik od f2 do f3". Alternatywą jest f1 (f2 (f3 (x))) z dużą ilością niechlujnych nawiasów klamrowych. – si14

6

Kompozycja funkcji nie jest superpopularną operacją w Pythonie, zwłaszcza w taki sposób, że operator kompozycji jest wyraźnie potrzebny. Jeśli coś zostało dodane, nie jestem pewien, czy podoba mi się wybór << i >> dla Pythona, które nie są dla mnie tak oczywiste, jak im się wydaje.

Podejrzewam, że wiele osób będzie bardziej komfortowe z funkcją compose, których kolejność nie jest problematyczne: compose(f, g)(x) oznaczałoby f(g(x)), takiej samej kolejności jak w matematyce o i . w Haskell. Python stara się unikać interpunkcji, gdy angielskie słowa będą robić, szczególnie gdy znaki specjalne nie mają powszechnie znanego znaczenia. (Wyjątki dotyczą rzeczy, które wydają się zbyt przydatne do pominięcia, takich jak @ dla dekoratorów (z dużym wahaniem) oraz * i ** dla argumentów funkcji.)

Jeśli zdecydujesz się wysłać to do pomysłów Pythona, Prawdopodobnie wygrasz o wiele więcej osób, jeśli znajdziesz jakieś przykłady w bibliotekach stdlib lub popularnych w Pythonie, których skład funkcji mógłby sprawić, że kod byłby bardziej przejrzysty, łatwiejszy do napisania, utrzymywany lub efektywny.

+0

Czy możesz wyjaśnić, dlaczego '>>' i '<<' nie są oczywiste? Nie kłócę się, tylko próbuję sprawdzić, czy istnieje consensus wśród programistów. – max

+0

@max, nie mam pojęcia, jak ktoś opisuje, dlaczego coś jest _nie oczywiste. Nie ma po prostu powodu, żebym spojrzał na '<<' and '>>' i powiedziałbym: "Ach, powinny być użyte do tego!" –

+0

@ Podejmij próbę: jest * mniej oczywisty * z powodu * braku tradycji * tego użycia. Również * mniej oczywiste * ze względu na to, że * w innym znaczeniu * '>>' jest używane w rozszerzonej instrukcji drukowania ("print chevron"). Również * nieoczywiste * powiązanie [operacje zmiany] (https://docs.python.org/2/reference/expressions.html#shifting-operations) [funkcji specjalnych] (https://docs.python.org/ 2/reference/datamodel.html # emulating-numeric-types) do kompozycji dla * all * [callables] (https://docs.python.org/2/reference/datamodel.html#emulating-callable-objects): * nie jest jasne, jakie rodzaje przedmiotów to zdyskwalifikują *. – n611x007

6

Można to zrobić z reduce, chociaż kolejność połączeń jest od lewej do prawej tylko:

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 
1

nie mam wystarczająco dużo doświadczenia z Python aby mieć pogląd na temat, czy zmiana języka, być wartościowym. Ale chciałem opisać opcje dostępne w obecnym języku.

Aby uniknąć tworzenia nieoczekiwane zachowanie, kompozycja funkcjonalne powinny idealnie zgodne ze standardem matematyczne (lub Haskell) kolejność operacji, to znaczy powinien f ∘ g ∘ h stosuje h, następnie g, następnie f.

Jeśli chcesz użyć istniejącego operatora w Pythonie, powiedz <<, jak wspomniałeś, że masz problem z lambdami i wbudowanymi. Możesz ułatwić sobie życie, definiując odbitą wersję __rlshift__ oprócz __lshift__. Dzięki temu zająć się obiektami lambda/wbudowanymi sąsiadującymi z obiektami composable.Kiedy masz dwa sąsiednie lambda/wbudowane, będziesz musiał jawnie przekonwertować (tylko jeden z nich) na composable, jak zasugerował @ si14. Uwaga: naprawdę mam na myśli __rlshift__, a nie __rshift__; w rzeczywistości odradzam używanie w ogóle __rshift__, ponieważ zmiana zamówienia jest myląca pomimo wskazówki kierunkowej zapewnianej przez kształt operatora.

Istnieje jednak inne podejście, które warto rozważyć. Ferdinand Jamitzky ma great recipe do definiowania operatorów pseudo-szumów w Pythonie, które działają nawet na wbudowanych. Dzięki temu możesz napisać f |o| g dla składu funkcji, który w rzeczywistości wygląda bardzo rozsądnie.

Powiązane problemy