Function overloading + default arguments issues

Monkey Forums/Monkey Programming/Function overloading + default arguments issues

clevijoki(Posted 2011) [#1]
Class A
End Class

Class B Extends A
End Class


Function F( base:A, arg:Int=0 )
	Print( "base func called" )
End Function

Function F( base:B, arg:Int=0 )
	Print( "extended func called" )
End Function

Function Main:Int()
	F( New B() )	' this does not work
	F( New B(), 0 )	' this works fine
End Function


This will fail to compile, it complains:
Error : Unable to determine overload to use: Function F:Int(Local base:A,Local arg:Int) or Function F:Int(Local base:B,Local arg:Int).

Also if I remove the default argument from F then it will force me to put the arguments in and it will work


marksibly(Posted 2011) [#2]
Hi,

It's because there is no 'exact' match for the first one - it could be passed to either F(), and the use of default params is always treated as an 'inexact' match.


clevijoki(Posted 2011) [#3]
Well that selection logic is flawed IMO, as there is only one valid F(), the compiler just can't figure it out. If it just compared against all functions named F it would find there is only one function that is valid.

I'm personally not a huge fan of default arguments, but it's probably too late to remove them from monkey now.


marksibly(Posted 2011) [#4]
HI,

> as there is only one valid F(),

Actually, both are valid in that both can be called. And I don't think it's immediately clear which F() the first call should invoke.

The logic to match overloads could be made 'cleverer', but at the cost of making it more complex, and I'm not a big fan of that.

The rules at the moment are pretty simple:

* If there's an exact match, use it.

* If there's ONE near match, use it.

* If the call involves a default, it is considered a 'near' match.

The 'defaults' exception is a bit different from other languages, but other languages don't support defaults as flexible as Monkey's - eg: in C++, defaults must be at the end of arg lists, and really just generate more overloads.

I actually don't mind having to be more explicit when calling overloads, possibly because I have been bitten in the past by c++'s 'lax' overloading rules!


clevijoki(Posted 2011) [#5]
Ah yes you are right they are both valid. One would be more specialized however and you could find that out by just evaluating all of the functions.

I like to code with overloads to direct logic statically instead of dynamically, it's just more efficient and structured. Because of this rule it means I can't use default arguments in any of these functions which is oddly arbitrary.

And how would you explicitly call this overload in monkey anyway?

That logic also breaks this case as well:

Class A
End Class

Class B Extends A
End Class

Class C Extends B
End Class

Function F(a:A)
	Print("A")
End Function

Function F(b:B)
	Print("B")
End Function

Function Main:Int()
	F(New C())
End Function



marksibly(Posted 2011) [#6]
Hi,

> Because of this rule it means I can't use default arguments in any of these functions

Well, you can, you just need to be a bit careful.

> That logic also breaks this case as well:

You need to cast, eg: F(A(New C())), which I'm OK with. Or you could add F(c:C).


clevijoki(Posted 2011) [#7]
So how in the first example do I use the default function arguments? It's already class B.

Let's say I worked around the second version by using F(B(New C())) and then two weeks later on I decided I needed an F(c:C) specialization after all, that means I would have have to remember where all my uses of F(B(New C())) are and change them in order to get them to specialize properly. What if somebody else decided to add F(c:C) and then had to waste time trying to figure out why it was never called?

Because I can't be sure that people haven't casted a class in order to make monkey compile, the mere act of adding a function overload is unreliable, rendering function overloading for anything but simple types dangerous and bug prone in monkey imo. Doesn't forcing you to explicitly select an overload kind of break the whole point of function overloading?


marksibly(Posted 2011) [#8]
Hi,

> So how in the first example do I use the default function arguments? It's already class B.

You can't, the defaults are making the call too ambiguous. You need to specify the default - or you could write the overloads more specifically, eg:

F(A)
F(A,int)
F(B)
F(B,int)

> two weeks later on I decided I needed an F(c:C)

This is an issue with overloading in any language - adding a new overload always has the potential to break existing code. This is esp. true in languages like C++ which have complex overload selection rules, to the point where you're not always sure what's being called.

> Doesn't forcing you to explicitly select an overload kind of break the whole point of function overloading?

Not IMO if it reduces ambiguity. I like to be able to look at a function call and have a good idea of what it's actually calling.

Bottom line for me is I'm fine with the way overloading works - it's useful without being C++-style error prone. Defaults DO complicate things a bit, but that's the price for more flexible defaults. Perhaps I've just never used overloads the way you do, but like I say, I have been bitten in the past by overly-clever use of overloading, so decided to keep things simple - and am happy with the results.


clevijoki(Posted 2011) [#9]
Well like I mentioned before, I am not a big fan of default arguments in general, if those remained broken I would not care. There are other issues with them, like redefining defaults in child classes, which can cause real problems.

Not selecting the most specialized function however, I think is really bad. An analogy would be with virtual function overriding:

Class A
	Method F:Void()
		Print("F(A)")
	End Method
End Class

Class B Extends A
	Method F:Void()
		Print("F(B)")
	End Method
End Class

Class C Extends B
End Class

Function G(a:A)
	Print("g(A)")
End Function

Function G(b:B)
	Print("g(B)")
End Function

Function Main:Int()
	Local c:C = new C()
	
	c.F()
	G(B(c))
End Function


You don't expect people to explicitly select overridden methods. Why expect explicitly selecting overloaded functions?

You should learn how to use a functional language like haskell or F#, because they use overloading as a basis for logic selection. The techniques learned in these languages can be used in most other languages too, except monkey apparently. Because this logic is done at compile time, not runtime, it has significant benefits for writing fast reliable code. Without a debugger, making something work without debugging is pretty critical.


Samah(Posted 2011) [#10]
You should learn how to use a functional language like haskell or F#...

Next build target for Monkey, Scala! ;)