Kari Python dan parsial

Aug 19 2020

Saat melakukan latihan pemrograman di codewars.com, saya menemukan latihan tentang fungsi kari dan parsial.

Menjadi seorang pemula dalam pemrograman dan baru pada topik tersebut, saya mencari di internet untuk informasi tentang topik tersebut dan cukup jauh untuk menyelesaikan latihan. Namun sekarang saya telah menemukan rintangan yang sepertinya tidak dapat saya atasi dan saya di sini mencari dorongan ke arah yang benar.

Latihannya agak sederhana: tulis sebuah fungsi yang dapat mengkurasi dan / atau memihak fungsi masukan apa pun dan mengevaluasi fungsi masukan tersebut setelah cukup banyak parameter masukan yang disediakan. Fungsi masukan dapat menerima sejumlah parameter masukan. Juga fungsi kari / parsial harus sangat fleksibel dalam cara memanggilnya, mampu menangani banyak, banyak cara berbeda untuk memanggil fungsi tersebut. Selain itu, fungsi kari / parsial diperbolehkan untuk dipanggil dengan lebih banyak input daripada yang dibutuhkan oleh fungsi input, dalam hal ini semua kelebihan input harus diabaikan.

Mengikuti tautan latihan, semua kasus pengujian dapat ditemukan yang harus dapat ditangani oleh fungsi tersebut.

Kode yang saya buat adalah sebagai berikut:

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

Sekarang kode ini gagal ketika tes berikut dijalankan:

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

di mana tambahkan = a + b + c (fungsi yang didefinisikan dengan benar), a = 1, b = 2, c = 3, dan jumlah = 6.

Alasan ini gagal adalah karena curry_partial(add, a)mengembalikan pegangan fungsi ke fungsi tersebut g. Pada panggilan kedua,, curry_partial(<function_handle to g>, b)kalkulasi minArgs = len(signature(func).parameters)tidak bekerja seperti yang saya inginkan, karena sekarang akan menghitung berapa banyak fungsi argumen masukan yang gdibutuhkan ( 1yaitu: yaitu *myArgs), dan bukan berapa banyak yang asli funcmasih memerlukan. Jadi pertanyaannya adalah, bagaimana saya bisa menulis kode saya sedemikian rupa sehingga saya dapat melacak berapa banyak argumen input yang funcmasih dibutuhkan asli saya (mengurangi angka itu setiap kali saya melakukan partialling fungsi dengan argumen awal yang diberikan).

Saya masih harus banyak belajar tentang pemrograman dan kari / parsial, jadi kemungkinan besar saya belum memilih pendekatan yang paling nyaman. Tapi saya ingin belajar. Kesulitan dalam latihan ini bagi saya adalah kombinasi parsial dan kari, yaitu melakukan loop kari sambil berpartisi setiap argumen awal yang ditemukan.

Jawaban

1 Ava Aug 20 2020 at 13:27

Coba ini.

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

Ini memang tidak terlalu bagus secara desain. Sebagai gantinya Anda harus menggunakan class, untuk melakukan semua pekerjaan internal seperti, misalnya, flip-flop (jangan khawatir kami tidak memerlukan flip-flop di sana ;-)).

Setiap kali ada fungsi yang mengambil argumen arbitrer, Anda selalu dapat membuat instance kelas itu dengan meneruskan fungsi tersebut. Tapi kali ini bagaimanapun, saya serahkan pada Anda.

Nishant Aug 21 2020 at 02:46

Saya tidak yakin tentang kari , tetapi jika Anda memerlukan generator fungsi parsial sederhana, Anda dapat mencoba sesuatu seperti ini:

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

Bahkan jika itu tidak menyelesaikan masalah asli Anda, lihat apakah Anda dapat menggunakan kode di atas untuk mengurangi pengulangan kode (saya tidak yakin, tapi saya pikir ada beberapa pengulangan kode di fungsi bagian dalam?); yang akan membuat masalah selanjutnya lebih mudah dipecahkan.

Mungkin ada fungsi di pustaka standar yang sudah menyelesaikan masalah ini. Banyak bahasa fungsional murni seperti Haskell memiliki fitur ini dibangun ke dalam bahasa.