Skip to content

Draft: Adding restriction to LPython generics #921

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

Closed
wants to merge 40 commits into from

Conversation

ansharlubis
Copy link
Collaborator

No description provided.

@certik
Copy link
Contributor

certik commented Aug 5, 2022

Awesome, great job!

Let's add some tests for error messages when a wrong type is provided.

@certik
Copy link
Contributor

certik commented Aug 21, 2022

(Ping me once tests pass and this is ready for review, or if you need help fixing anything.)

@ansharlubis
Copy link
Collaborator Author

ansharlubis commented Aug 21, 2022

@certik It passed the tests. Currently, there are four restrictions: SupportsPlus, SupportsZero, Divisible, and Any. The operations supported are still only addition and division on integers and reals.

The generic mean also worked in both CPython and LLVM.

case (ASR::binopType::Sub): { result = left_value - right_value; break; }
case (ASR::binopType::Mul): { result = left_value * right_value; break; }
// case (ASR::binopType::Sub): { result = left_value - right_value; break; }
// case (ASR::binopType::Mul): { result = left_value * right_value; break; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this commented out?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't made any tests with subtraction and multiplication and there is no restriction defined that corresponds to these operations yet.

@@ -346,6 +350,7 @@ cast_kind
| IntegerToReal
| LogicalToReal
| RealToReal
| TemplateToReal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is this cast used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in generics_list_01.py. The type of sum in the division sum/k is generic, it may or may not have to be cast into a real number for the division.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. It's casting to real, but we don't know from "what". Good for now. We can iterate on the design later.

T = TypeVar('T', bound=Any)

def f(x: T, y: T) -> T:
return x + y
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to brainstorm the name for this --- and whether to even allow it at all.

I believe in Rust (or Go?) "Any" means "any type" can be used, which means you are not allowed to do almost anything on it, specifically you are not allowed to do "a+b", because that does not work with any type, only with types that support it.

Also, how is this checked --- it seems that here we would still need to go into the template to check that the type can be used?

It seems to me we should not allow this at all. That is, "Any" could mean "any user type will work here", i.e., "no restrictions", but then you can't use "+" in the function, so the above should not compile. You would have to specify restrictions here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's an example of a restriction error. It wouldn't pass the type check. Maybe the name Any is a bit inappropriate and Assignable would be better for it.

The check is very simple, the binary operation x + y would require both x and y to have SupportsPlus in their bounds if they are type parameters.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I forgot that this is an "error", this is good. There is no problem.

Copy link
Contributor

@certik certik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent job on this!

There are a few design questions here, that we should brainstorm.

It seems to me that with this PR, we should always require restrictions. That is, when compiling the templated code, we either give an error message right away, or if not, then this can can always be instantiated without errors for any user type that satisfies the restrictions, and whether it satisfies the restrictions is determined at the call site.

@czgdp1807 let me know what you think as well.

@certik
Copy link
Contributor

certik commented Aug 21, 2022

Also should we call it restrictions or requirements? There are other possible names too.

@ansharlubis
Copy link
Collaborator Author

Yes, I'm following the original idea of having it strongly-typed, so that every type parameter has to be given the appropriate restrictions. Thinking from the way I normally program in Python, it would feel a bit unfriendly towards the users to have to specify every single restriction. Then again, I'm not really sure how much the trade-off of having to type check every function instantiation is.

@certik
Copy link
Contributor

certik commented Aug 21, 2022

Another word is "constraint".

To make it friendly, we have to create predefined restrictions like Number, or String, for the most used cases. I would keep the feature that you can fully check everything at the call site, without going into the function. It's called "strong concepts". The question is whether to allow "weak concepts" or "no concepts", where you are required to go into the templated function at each call site, and I would not allow that at all.

I am looking into how Go now does it:

https://go.dev/doc/tutorial/generics
https://go.dev/blog/intro-generics

They call it "constraint". It seems you cannot use generics without constraints (restrictions).

Copy link
Contributor

@certik certik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good to go in.

After the git history is polished.

@czgdp1807, can you have a look too?

There are small things that we can fix up later. Is there anything major that needs fixing before we can merge this?

@certik certik requested a review from czgdp1807 August 21, 2022 13:16
@certik
Copy link
Contributor

certik commented Aug 21, 2022

(I resolved the conflicts with master, hopefully correctly.)

@certik
Copy link
Contributor

certik commented Aug 21, 2022

I screwed it up with my "conflicts resolve":

/home/runner/work/lpython/lpython/lpython-0.3.0-702-g953fa7bc8/src/lpython/semantics/python_ast_to_asr.cpp:3000:60: error: ‘value’ was not declared in this scope
 3000 |             ASR::ttype_t *value_type = ASRUtils::expr_type(value);
      |                                                            ^~~~~

@certik
Copy link
Contributor

certik commented Aug 21, 2022

@ansharlubis do you want to push in a fix? I think just the variable needs to be renamed. I don't have time right now.

from ltypes import TypeVar, SupportsPlus, SupportsZero, Divisible
from ltypes import f64, i32

T = TypeVar('T', bound=SupportsPlus|SupportsZero|Divisible)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the meaning of SupportsPlus, SupportsZero and Divisible?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we write these using expressions as well? I will make the meaning of my question more clear once I know the answer to the above comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It says that the template T can do such operations. SupportsPlus means you can do T+T -> T. SupprtsZero means you can do T = 0. Divisible means you can do T / 5.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. T + T -> T makes sense. Why do we want to specify T = 0 statement and T/5. Why can't we do T = TypeVar('T', bound=T + T -> T | T/T -> T)? How many more such specifications would be needed. Will we support SupportsMul, SupportsDivide, SupportsStringConcat or in general, Supports[Op] where [Op] can be replaced the operation the type T will support .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is the plan to just list all the operations: #984, but that will come later. First we wanted an initial constraints operations done, which this PR delivers. Then we will refine it, figure out the syntax and all the details how to just specify operations (and it seems that yes, you need "T = 0" as well), etc.

This PR just needs to be good enough, no bad choices that would be hard to improve upon. But it does not implement the final design, just like previous merged PRs did not implement the final design either, it just moved us towards the final design.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@czgdp1807 It's still the prototype for the restriction, I got it working with some hard-coded restrictions, but I believe that we will change the design later so that restriction can be freely defined by users.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@czgdp1807 is this ok to merge?

(We still need to polish the history.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Makes sense. Feel free to merge.

@ansharlubis
Copy link
Collaborator Author

@certik I just saw the problem, I'll handle it today.

@certik
Copy link
Contributor

certik commented Aug 22, 2022

@ansharlubis would you mind polishing the git history (feel free to send a new PR). I'll do a final review and we can merge.

@certik
Copy link
Contributor

certik commented Aug 23, 2022

I restarted the macOS test, which failed for an unrelated reason.

@ansharlubis ansharlubis deleted the add-restriction branch August 24, 2022 07:59
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 this pull request may close these issues.

3 participants