-
Notifications
You must be signed in to change notification settings - Fork 170
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
Conversation
Awesome, great job! Let's add some tests for error messages when a wrong type is provided. |
(Ping me once tests pass and this is ready for review, or if you need help fixing anything.) |
@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; } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
Also should we call it restrictions or requirements? There are other possible names too. |
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. |
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 They call it "constraint". It seems you cannot use generics without constraints (restrictions). |
There was a problem hiding this 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?
(I resolved the conflicts with master, hopefully correctly.) |
I screwed it up with my "conflicts resolve":
|
@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) |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 .
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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.
@certik I just saw the problem, I'll handle it today. |
@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. |
I restarted the macOS test, which failed for an unrelated reason. |
No description provided.