Python curry i częściowe

Aug 19 2020

Robiąc ćwiczenia programistyczne na codewars.com, natknąłem się na ćwiczenie z curry i funkcji częściowych.

Będąc nowicjuszem w programowaniu i nowym tematem, szukałem w Internecie informacji na ten temat i zająłem się rozwiązaniem zadania. Jednak teraz natknąłem się na przeszkodę, której nie mogę pokonać i szukam tu posunięcia we właściwym kierunku.

Ćwiczenie jest raczej proste: napisz funkcję, która może sprawdzić i / lub podzielić dowolną funkcję wejściową i ocenia funkcję wejściową po dostarczeniu wystarczającej ilości parametrów wejściowych. Funkcja wejściowa może przyjmować dowolną liczbę parametrów wejściowych. Również funkcja curry / częściowa powinna być bardzo elastyczna w sposobie jej wywoływania, będąc w stanie obsłużyć wiele, wiele różnych sposobów wywoływania funkcji. Ponadto funkcja curry / częściowa może być wywoływana z większą liczbą danych wejściowych niż jest to wymagane przez funkcję wejściową, w takim przypadku wszystkie nadmiarowe dane wejściowe muszą zostać zignorowane.

Po odsyłaczu do ćwiczeń można znaleźć wszystkie przypadki testowe, które funkcja musi obsługiwać.

Kod, który wymyśliłem, jest następujący:

from functools import partial
from inspect import signature

def curry_partial(func, *initial_args):
    """ Generates a 'curried' version of a function. """

    # Process any initial arguments that where given. If the number of arguments that are given exceeds 
    # minArgs (the number of input arguments that func needs), func is evaluated

    minArgs = len(signature(func).parameters)
    if initial_args:
        if len(initial_args) >= minArgs: 
            return func(*initial_args[:minArgs])

        func = partial(func, *initial_args)
        minArgs = len(signature(func).parameters)

    
    # Do the currying
    def g(*myArgs):
        nonlocal minArgs

        # Evaluate function if we have the necessary amount of input arguments
        if minArgs is not None and minArgs <= len(myArgs):
                return func(*myArgs[:minArgs]) 
            
        def f(*args):
            nonlocal minArgs
            newArgs = myArgs + args if args else myArgs

            if minArgs is not None and minArgs <= len(newArgs):
                return func(*newArgs[:minArgs])
            else:
                return g(*newArgs)  
        return f
    return g

Teraz ten kod kończy się niepowodzeniem, gdy wykonywany jest następujący test:

test.assert_equals(curry_partial(curry_partial(curry_partial(add, a), b), c), sum)

gdzie add = a + b + c (poprawnie zdefiniowana funkcja), a = 1, b = 2, c = 3, a sum = 6.

Przyczyną niepowodzenia jest to, że curry_partial(add, a)zwraca uchwyt funkcji do funkcji g. W drugim wywołaniu curry_partial(<function_handle to g>, b)obliczenie minArgs = len(signature(func).parameters)nie działa tak, jak bym chciał, ponieważ oblicza teraz, ile argumentów wejściowych gwymaga funkcja ( 1czyli: tj. *myArgs), A nie ile funcwymaga jeszcze oryginał . Pytanie brzmi więc, w jaki sposób mogę napisać swój kod tak, aby móc śledzić, ile argumentów wejściowych funcnadal potrzebuje mój pierwotny (zmniejszając tę ​​liczbę za każdym razem, gdy częściowo dzielę funkcję z podanymi argumentami początkowymi).

Nadal muszę się wiele nauczyć na temat programowania i curry / częściowego, więc najprawdopodobniej nie wybrałem najwygodniejszego podejścia. Ale chciałbym się nauczyć. Trudność w tym ćwiczeniu polega dla mnie na połączeniu częściowych i curry, tj. Wykonywanie pętli curry podczas częściowego przedstawiania wszelkich napotkanych argumentów początkowych.

Odpowiedzi

1 Ava Aug 20 2020 at 13:27

Wypróbuj to.

from inspect import signature

# Here `is_set` acts like a flip-flop
is_set = False
params = 0

def curry_partial(func, *partial_args):
    """
    Required argument: func
    Optional argument: partial_args
    Return:
        1) Result of the `func` if
           `partial_args` contains
           required number of items.
        2) Function `wrapper` if `partial_args`
           contains less than the required
           number of items.
    """

    global is_set, params
    
    if not is_set:
        is_set = True
        
        # if func is already a value
        # we should return it
        try: params = len(signature(func).parameters)
        except: return func
    
    try:
        is_set = False
        return func(*partial_args[:params])
    
    except:
        is_set = True
    
        def wrapper(*extra_args):
            """
            Optional argument: extra_args
            Return:
                1) Result of the `func` if `args`
                   contains required number of
                   items.
                2) Result of `curry_partial` if
                   `args` contains less than the
                   required number of items.
            """
            
            args = (partial_args + extra_args)
            
            try:
                is_set = False
                return func(*args[:params])
            except:
                is_set = True
                return curry_partial(func, *args)
    
    return wrapper

To rzeczywiście nie jest zbyt dobre z założenia. Zamiast tego powinieneś użyć class, do wszystkich wewnętrznych prac, takich jak na przykład przerzutnik (nie martw się, nie potrzebujemy tam żadnego przerzutnika ;-)).

Zawsze, gdy istnieje funkcja, która przyjmuje dowolne argumenty, zawsze można utworzyć wystąpienie tej klasy, przekazując funkcję. Ale tym razem zostawiam to na tobie.

Nishant Aug 21 2020 at 02:46

Nie jestem pewien co do curry , ale jeśli potrzebujesz prostego generatora funkcji częściowych, możesz spróbować czegoś takiego:

from functools import partial
from inspect import signature

def execute_or_partial(f, *args):
    max = len(signature(f).parameters)
    if len(args) >= max: 
        return f(*args[:max])
    else:
        return partial(f, *args)

s = lambda x, y, z: x + y + z

t = execute_or_partial(s, 1)
u = execute_or_partial(t, 2)
v = execute_or_partial(u, 3)

print(v)

or

print(execute_or_partial(execute_or_partial(execute_or_partial(s, 1), 2), 3))

Nawet jeśli nie rozwiąże to twojego pierwotnego problemu, sprawdź, czy możesz użyć powyższego kodu, aby zmniejszyć liczbę powtórzeń kodu (nie jestem pewien, ale myślę, że w funkcji wewnętrznej jest jakieś powtórzenie kodu?); ułatwi to rozwiązanie kolejnych problemów.

W bibliotece standardowej mogą znajdować się funkcje, które już rozwiązują ten problem. Wiele czysto funkcjonalnych języków, takich jak Haskell, ma tę funkcję wbudowaną w język.