Currying Python et partiel

Aug 19 2020

En faisant des exercices de programmation sur codewars.com, j'ai rencontré un exercice sur le curry et les fonctions partielles.

Étant novice en programmation et nouveau sur le sujet, j'ai cherché sur Internet des informations sur le sujet et je suis allé assez loin dans la résolution de l'exercice. Cependant, je suis maintenant tombé sur un obstacle que je n'arrive pas à surmonter et je cherche ici un coup de pouce dans la bonne direction.

L'exercice est assez simple: écrire une fonction qui peut curry et / ou partiel toute fonction d'entrée et évalue la fonction d'entrée une fois que suffisamment de paramètres d'entrée sont fournis. La fonction d'entrée peut accepter n'importe quel nombre de paramètres d'entrée. De plus, la fonction curry / partial devrait être très flexible dans la façon dont elle est appelée, étant capable de gérer de très nombreuses façons d'appeler la fonction. De plus, la fonction curry / partial peut être appelée avec plus d'entrées que ce qui est requis par la fonction d'entrée, dans ce cas, toutes les entrées en excès doivent être ignorées.

En suivant le lien exercice, vous trouverez tous les cas de test que la fonction doit pouvoir gérer.

Le code que j'ai trouvé est le suivant:

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

Maintenant, ce code échoue lorsque le test suivant est exécuté:

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

où add = a + b + c (fonction correctement définie), a = 1, b = 2, c = 3 et sum = 6.

La raison pour laquelle cela échoue est parce que curry_partial(add, a)renvoie un descripteur de fonction à la fonction g. Dans le deuxième appel, curry_partial(<function_handle to g>, b)le calcul minArgs = len(signature(func).parameters)ne fonctionne pas comme je le souhaite, car il calculera maintenant le nombre d'arguments d'entrée requis par la fonction g(ce qui est 1: c'est-à-dire *myArgs), et non le nombre d'arguments requis par l'original func. La question est donc de savoir comment écrire mon code de manière à pouvoir suivre le nombre d'arguments d'entrée dont mon original a funcencore besoin (en réduisant ce nombre chaque fois que je partitionne la fonction avec des arguments initiaux donnés).

J'ai encore beaucoup à apprendre sur la programmation et le curry / partiel, donc je n'ai probablement pas choisi l'approche la plus pratique. Mais j'aimerais apprendre. La difficulté dans cet exercice pour moi est la combinaison de partial et curry, c'est-à-dire faire une boucle de curry tout en partitionnant les arguments initiaux rencontrés.

Réponses

1 Ava Aug 20 2020 at 13:27

Essayez ceci.

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

Ce n'est en effet pas très bon de par sa conception. Au lieu de cela, vous devriez utiliser class, pour faire tous les travaux internes comme, par exemple, la bascule (ne vous inquiétez pas, nous n'avons pas besoin de bascule ;-)).

Chaque fois qu'une fonction prend des arguments arbitraires, vous pouvez toujours instancier cette classe en passant la fonction. Mais cette fois cependant, je vous laisse cela.

Nishant Aug 21 2020 at 02:46

Je ne suis pas sûr du curry , mais si vous avez besoin d'un simple générateur de fonctions partielles, vous pouvez essayer quelque chose comme ceci:

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))

Même si cela ne résout pas votre problème d'origine, voyez si vous pouvez utiliser le code ci-dessus pour réduire la répétition du code (je ne suis pas sûr, mais je pense qu'il y a une certaine répétition de code dans la fonction interne?); cela facilitera la résolution des problèmes ultérieurs.

Il se peut que certaines fonctions de la bibliothèque standard résolvent déjà ce problème. De nombreux langages fonctionnels purs comme Haskell ont cette fonctionnalité intégrée dans le langage.