Skip to content

Figure out how to handle generic functions #141

Closed

Description

@certik

So that you can implement a function sin(x) in LPython, that would work both for f32 and f64.

The tricky part is to design this in such a way so that it works with CPython also.

Activity

  1. certik commented on Feb 14, 2022

    @certik
    ContributorAuthor

    So one good design might be:

    from ltypes import generic_function
    
    @generic_function("sin")
    def sin_f32(x: f32) -> f32:
        ...
    
    @generic_function("sin")
    def sin_f64(x: f64) -> f64:
        ...

    And we implement the generic_function decorator in ltypes.py to be used by CPython. Then LPython has understanding of this decorator and it creates GenericProcedure ASR type for sin.

  2. certik commented on Feb 22, 2022

    @certik
    ContributorAuthor

    Python has typing.overload decorator: https://docs.python.org/3/library/typing.html#typing.overload, which is almost what we want. We could do ltypes.overload:

    from ltypes import overload
    
    @overload
    def sin(x: f32) -> f32:
        ...
    
    @overload
    def sin(x: f64) -> f64:
        ...

    The CPython implementation of the overload decorator might use some global dictionary/mapping where it stores all the overloads and then returns a sin function that will consult this dictionary to call the proper overload based on the argument. The LPython implementation would just create a GenericProcedure in ASR.

  3. Smit-create commented on Feb 23, 2022

    @Smit-create
    Collaborator

    Here is the example of python's overload:

    from typing import overload
    
    @overload
    def foo(x: int) -> int: ...
    
    @overload
    def foo(x: str) -> str: ...
    
    def foo(x: str | int) -> str | int:
        return x
    
    print(type(foo(2)), type(foo('2')))

    Output:
    <class 'int'> <class 'str'>

    For this we need to add the ellipsis (#146) both in AST and ASR.

  4. Smit-create commented on Feb 23, 2022

    @Smit-create
    Collaborator

    I tried implementing the overload operator in pure python which works in much similar fashion using this following script:

    Python Script

    from inspect import getfullargspec, getcallargs
    
    global_map = {}
    
    class Dummy:
        def __init__(self, name):
            self.func_name = name
    
        def __call__(self, *args, **kwargs):
            func_map_list = global_map.get(self.func_name, False)
            if not func_map_list:
                raise Exception("Function not defined")
            for item in func_map_list:
                func, key = item
                try:
                    ann_dict = getcallargs(func, *args, **kwargs)
                except:
                    continue
                f = True
                for k, v in ann_dict.items():
                    if not key.annotations.get(k, False):
                        f = False
                        break
                    else:
                        if type(v) != key.annotations.get(k):
                            f = False
                            break
                if f:
                    return func(*args, **kwargs)
            raise Exception("Function not found with matching signature")
    
    
    def overload(f):
        dummy = Dummy(f.__name__)
        f_list = global_map.get(f.__name__, [])
        f_list.append((f, getfullargspec(f)))
        global_map[f.__name__] = f_list
        return dummy
    
    @overload
    def foo(a:int, b:int):
        return a*b
    
    @overload
    def foo(a:int):
        return a**2
    
    print(foo(2), foo(2, 10))

    It seems to be working fine, though it might require a bit of more testing.

  5. certik commented on Feb 23, 2022

    @certik
    ContributorAuthor

    Perfect! Please submit it as a PR, put overload into ltypes and put the tests into integration_tests/test_generics_01.py. We'll also extend our test runner to skip LPython tests and only run CPython one on this test until we implement it.

  6. certik commented on Feb 23, 2022

    @certik
    ContributorAuthor

    Couple comments to the implementation:

    • rename Dummy to something more descriptive. Perhaps OverloadedFunction?
    • Don't use empty except. I think you don't have to use except at all there, getcallargs should not fail, or if it can, let's catch the specific exception.
    • Move global_map into a class variable, which is effectively still global, shared across all instances
    • Add type annotations for return values to the tests (foo)

    Otherwise this looks great.

  7. Smit-create commented on Feb 26, 2022

    @Smit-create
    Collaborator

    I'm now thinking to extend the overload support to LPython.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions