-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
[Feature Request] Clarify Function Overloading Resolution #1321
Comments
I agree some classification is needed, a clear spec will be greatly helpful. I don't think " the closest match" will break type safety though, as it likely refers to calling the overload that requires the least number of implicit type conversion. Similar behaviour can be seen in C . However, I do worry the full C behaviour is a bit too complicated and ad hoc. |
Mojo currently uses a very C style of overload resolution, where it counts the number of implicit conversions required for a candidate to work out. If two different candidates have the same number of conversions, then an ambiguity error is emitted. Some examples:
agree we should document this! |
I'm a bit confused/surprised here by the reference to implicit conversions--is Mojo supposed to be a weakly (or at least somewhat weakly) typed language? That would be a pretty huge change from Python, and the other major machine learning languages (R and Julia), which only have implicit conversions for numeric types (numbers/integers/floats). |
On the subject of using the sum of distances by argument as the metric for choosing the closest method, that seems like it would work and be familiar to C users, but it's also very counterintuitive because it's implicit, rather than explicit, and can be very nonlocal--for instance, say we have a hierarchy with types fn test(a: float, b: float, c: float, d):
# somewhere off in another part of the code
fn test(a: real, b: real, c: real, d: real): Then, the second My own thought: I'm familiar with overloading in both R and Julia. It's been a while since I touched S4, so I can't 100% remember the details of how this is resolved, but Julia's behavior is very intuitive for me: overloading a function this way would create an error, because neither signature "dominates" the other (where signature fn test(a: float, b: float, c: float, d):
fn test(a: real, b: real, c: real, d: real):
# if the next method is omitted,
# compiler or linter should give an error
fn test(a: float, b: float, c: float, d:real) |
@ParadaCarleton I don't think implicit conversion will make the language weakly typed in anyway, Mojo just calls the corresponding constructor for you. Even "excessively" typed language like Coq supports some form of implicit coercion. It's quite useful when you want to use literals while calling (say numeric) functions and initialising variables. I agree it can be confusing at times and should be used sparingly, though. See here for more discussion, and suggestion for future improvements.
If I understand correctly, Mojo's ITC (implicit type conversion) doesn't work this way (at least for now, for the more tricky cases are blocked by traits). The ITC graph is just a plain directed graph (not even a DAG), and is not at all required to be totally ordered by topological ordering, so it may not collapse to the above chain. In the following example, you may also see that # ℕ
# ↓
# A
# ↙ ↘
# B C
# ↘ ↗
# D
@value
@register_passable("trivial")
struct A:
fn __init__(n: IntLiteral) -> Self: return A {}
@value
struct B:
fn __init__(inout self, a: A): ...
@value
struct C:
fn __init__(inout self, b: A): ...
fn __init__(inout self, d: D): ... # doesn't need to form a DAG
@value
struct D:
fn __init__(inout self, c: B): ...
fn main():
let a: A = 0 # powered by implicit conversion™
let b: B = a
let c: C = a
let d: D = b
let d0: D = a # error: cannot implicit convert 'A' value to 'D'
fn f[T: AnyType](x: A, y: A, z: T):
print("AAT")
fn f(x: B, y: B, z: B):
print("BBB")
f(a, a, a) # prints "AAT" |
I'm happy with literals being automatically converted/inferred. For instance, in
I'm not sure what you mean here--doesn't the type hierarchy have to be a DAG for multiple inheritance to work and stay compatible with Python?
Right! Sorry if I wasn't clear, but that's basically what I was trying to say: most users probably don't want to overload functions like that, but it could easily happen accidentally. So instead, this should throw an error to make users clarify what they want. The problem is As an example, we could have something like |
Agreed. Please checkout this discussion, and help us push for more control over implicit conversions.
I think it is true, but that's in the realm of
If we assume implicit conversion follow inheritance hierarchy, which they don't seem to. It's no easy task to determine the most specific type, Julia had problem with this before. Things may have improved since, IDK. I don't like the C style overload resolution, but at least it's sound™.
This feels like bad API design. Mojo demands more type discipline than e.g. Julia, and I see no point (and not possible) in leaving out the type annotation for That said, the kind of hijacking can happen to very naive looking code (in Mojo 0.5.0). Just because we have a constructor from fn plus(a: Int, b: Int, c: String) -> Int:
return a b
fn plus(a: Float64, b: Float64, c: Float64) -> Float64:
return a b c
plus(1, 1, 1) # calls the first one |
Review Mojo's priorities
What is your request?
What does "Closest match" mean? To me, it sounds like you're proposing to let functions call methods that have types that "almost match" but don't. That would break type safety and definitely cause a lot of bugs.
Is this supposed to be a version of the "Most specific type" rule used by Julia and most implementations of multiple dispatch (the dynamic-typing equivalent of function overloading)?
What is your motivation for this change?
Clarity.
Any other details?
No response
The text was updated successfully, but these errors were encountered: