Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Figure out how to handle generic functions #141

Closed
certik opened this issue Feb 14, 2022 · 7 comments · Fixed by #187
Closed

Figure out how to handle generic functions #141

certik opened this issue Feb 14, 2022 · 7 comments · Fixed by #187

Comments

@certik
Copy link
Contributor

certik commented Feb 14, 2022

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.

@certik
Copy link
Contributor Author

certik commented Feb 14, 2022

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.

@certik certik mentioned this issue Feb 21, 2022
9 tasks
@certik
Copy link
Contributor Author

certik commented Feb 22, 2022

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.

@Smit-create
Copy link
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.

@Smit-create
Copy link
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.

@certik
Copy link
Contributor Author

certik commented Feb 23, 2022

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.

@certik
Copy link
Contributor Author

certik commented Feb 23, 2022

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.

@Smit-create
Copy link
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
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants