www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Default argument values

reply bearophile <bearophileHUGS lycos.com> writes:
This page talks about default function argument values in C#4:
http://www.lostechies.com/blogs/jimmy_bogard/archive/2010/05/18/caveats-of-c-4-0-optional-parameters.aspx


Near the end it says:

If we change the value of the optional argument from 0 to 1, we have to
recompile all calling code for the calling code to get the updated value! For
folks shipping assemblies for a living, this means that optional argument
values don't version well, as callers have to recompile.  When I used to work
for a company whose product included a DLL, we avoided optional method
arguments for just this reason. It's not a reason not to use optional
arguments, but it's important to understand how they work so that you don't run
into headaches later.<

D too copies the default value at the calling point. Looking at this from the eyes of a Python programmer this looks like a dirty hack. This can be a cause of problems in D dlls too, I am not sure. The related discussion on Reddit: http://www.reddit.com/r/programming/comments/c5st2/caveats_of_c_40_optional_parameters/ Someone has given this answer:
Have you looked at Scala's implementation on the JVM? It's very simple. Each
default parameter generates a static method with a predictable name (e.g.
foo$default$3 for parameter 3 of method foo) that may be called to compute the
default value of an unsupplied parameter. With this approach, changing the
default value of an existing default-valued parameter means changing the
implementation of a static method without changing its signature, so there is
no need for callers to be recompiled. Because of JIT inlining, the performance
should be the same as C#'s earlier-bound approach.<

You can find more info about this Scala feature here: http://www.scala-lang.org/sid/1# I copy here how it's implemented:

def f(a: Int = 1, b: String) // generates a method: def f$default$1 = 1 f(b = "3") // transformed to: f(b = "3", a = f$default$1) A default argument may be an arbitrary expression. Since the scope of a parameter extends over all subsequent parameter lists (and the method body), default expressions can depend on parameters of preceding parameter lists (but not on other parameters in the same parameter list). Note that when using a default value which depends on earlier parameters, the actual arguments are used, not the default arguments. < Bye, bearophile
May 20 2010
next sibling parent "Nick Sabalausky" <a a.a> writes:
"bearophile" <bearophileHUGS lycos.com> wrote in message 
news:ht47l8$gfj$1 digitalmars.com...
 This page talks about default function argument values in C#4:
 http://www.lostechies.com/blogs/jimmy_bogard/archive/2010/05/18/caveats-of-c-4-0-optional-parameters.aspx


 Near the end it says:

If we change the value of the optional argument from 0 to 1, we have to 
recompile all calling code for the calling code to get the updated value! 
For folks shipping assemblies for a living, this means that optional 
argument values don't version well, as callers have to recompile.  When I 
used to work for a company whose product included a DLL, we avoided 
optional method arguments for just this reason. It's not a reason not to 
use optional arguments, but it's important to understand how they work so 
that you don't run into headaches later.<

D too copies the default value at the calling point. Looking at this from the eyes of a Python programmer this looks like a dirty hack. This can be a cause of problems in D dlls too, I am not sure.

I dunno, a caller might rely on a default value being what it was when they had compiled it. Forcing them to get a new version of the library and recompile increases the chance that they're notice that the new value is different, either via a "this has changed" in the docs, or when they run it after recompiling with the new version of the library. Not sure if my argument really holds up or not, but just a thought.
May 20 2010
prev sibling next sibling parent Brad Roberts <braddr slice-2.puremagic.com> writes:
Most inlining is going to cause exactly the same class of problems, which 
nearly every shipping application is going to do.  Converting these to 
helpers functions will result in a tiny little function that will be 
inlined, so, right back to the same basic problem.

Later,
Brad

On Thu, 20 May 2010, bearophile wrote:

 This page talks about default function argument values in C#4:
 http://www.lostechies.com/blogs/jimmy_bogard/archive/2010/05/18/caveats-of-c-4-0-optional-parameters.aspx
 
 
 Near the end it says:
 
If we change the value of the optional argument from 0 to 1, we have to
recompile all calling code for the calling code to get the updated value! For
folks shipping assemblies for a living, this means that optional argument
values don't version well, as callers have to recompile.  When I used to work
for a company whose product included a DLL, we avoided optional method
arguments for just this reason. It's not a reason not to use optional
arguments, but it's important to understand how they work so that you don't run
into headaches later.<

D too copies the default value at the calling point. Looking at this from the eyes of a Python programmer this looks like a dirty hack. This can be a cause of problems in D dlls too, I am not sure. The related discussion on Reddit: http://www.reddit.com/r/programming/comments/c5st2/caveats_of_c_40_optional_parameters/ Someone has given this answer:
Have you looked at Scala's implementation on the JVM? It's very simple. Each
default parameter generates a static method with a predictable name (e.g.
foo$default$3 for parameter 3 of method foo) that may be called to compute the
default value of an unsupplied parameter. With this approach, changing the
default value of an existing default-valued parameter means changing the
implementation of a static method without changing its signature, so there is
no need for callers to be recompiled. Because of JIT inlining, the performance
should be the same as C#'s earlier-bound approach.<

You can find more info about this Scala feature here: http://www.scala-lang.org/sid/1# I copy here how it's implemented:

def f(a: Int = 1, b: String) // generates a method: def f$default$1 = 1 f(b = "3") // transformed to: f(b = "3", a = f$default$1) A default argument may be an arbitrary expression. Since the scope of a parameter extends over all subsequent parameter lists (and the method body), default expressions can depend on parameters of preceding parameter lists (but not on other parameters in the same parameter list). Note that when using a default value which depends on earlier parameters, the actual arguments are used, not the default arguments. < Bye, bearophile

May 20 2010
prev sibling next sibling parent reply Adam Ruppe <destructionator gmail.com> writes:
On 5/20/10, bearophile <bearophileHUGS lycos.com> wrote:
 A default argument may be an arbitrary expression.

What I do in code where I want this kind of behaviour is something like this: void func(Whatever* options = null) { if(options is null) options = Whatever(default blah blah); } (substitute null for any invalid amount for the parameter. I recently did some timezone functions that used int.min as the default offset, meaning load it from the global.) If I want to change the Whatever default, I change it there and it still works out. Nothing fancy required. On 5/20/10, Nick Sabalausky <a a.a> wrote:
 I dunno, a caller might rely on a default value being what it was when they
 had compiled it.

Eh, if you leave the argument off the list, you're saying "I don't really care what this is", so you shouldn't worry about changes... key word being "shouldn't". I know I've broken this rule before, so having to recompile at least would be nice, like you say.
May 20 2010
next sibling parent reply div0 <div0 users.sourceforge.net> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Adam Ruppe wrote:
 On 5/20/10, bearophile <bearophileHUGS lycos.com> wrote:
 A default argument may be an arbitrary expression.

What I do in code where I want this kind of behaviour is something like this: void func(Whatever* options = null) { if(options is null) options = Whatever(default blah blah); } (substitute null for any invalid amount for the parameter. I recently did some timezone functions that used int.min as the default offset, meaning load it from the global.) If I want to change the Whatever default, I change it there and it still works out. Nothing fancy required. On 5/20/10, Nick Sabalausky <a a.a> wrote:
 I dunno, a caller might rely on a default value being what it was when they
 had compiled it.

Eh, if you leave the argument off the list, you're saying "I don't really care what this is", so you shouldn't worry about changes... key word being "shouldn't". I know I've broken this rule before, so having to recompile at least would be nice, like you say.

Yeah, I've gone off default arguments, there is rarely a defensibly good default argument, and you can achieve exactly the same functionality with overloads. C# dropped them, and when I do c# I don't miss default args. Either way of doing them sucks; better just to have a properly documented interface. - -- My enormous talent is exceeded only by my outrageous laziness. http://www.ssTk.co.uk -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.7 (MingW32) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/ iD8DBQFL9an0T9LetA9XoXwRAla5AKCXEizxgvbcNHVWwn+HgRRtukC8RACeJEFc 7wUACeT+f3WGTOLZpBGNcys= =unAD -----END PGP SIGNATURE-----
May 20 2010
parent "Nick Sabalausky" <a a.a> writes:
"div0" <div0 users.sourceforge.net> wrote in message 
news:ht49le$jgu$1 digitalmars.com...
 Yeah, I've gone off default arguments, there is rarely a defensibly good
 default argument, and you can achieve exactly the same functionality
 with overloads.

I often use default args as a shortcut for overloads (where applicable). And I've often had cases where default args were useful for getting a good combination of "super-simple interface for typical cases" and "high configurability for when it's actually needed".
May 20 2010
prev sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Adam Ruppe:

 void func(Whatever* options = null) {
     if(options is null)
          options = Whatever(default blah blah);
 }
 
 (substitute null for any invalid amount for the parameter. I recently
 did some timezone functions that used int.min as the default offset,
 meaning load it from the global.)
 
 If I want to change the Whatever default, I change it there and it
 still works out. Nothing fancy required.

In ShedSkin (a kind of Python compiler) I plan to do something like that automatically. The compiler creates a nullable value for each value that has a default, plus the if+assign inside the function. The nullable is done compactly, with an extra uint argument that encodes what arguments are actually given or what ones are not given and need to be initialized inside the function. So if you write code like this: void foo(int x, string s="hello", int y=10) { } void main() { foo(1, "red", 2); foo(3, "green"); foo(4); } It is syntax sugar for: void foo(int x, string s, int y, uint nDefaults) { if (nDefaults >= 1) y = 10; if (nDefaults >= 2) s = "hello"; } void main() { foo(1, "red", 2, 0); foo(3, "green", int.init, 1); foo(4, string.init, int.init, 2); } This is not exactly like the Python semantics, because in Python default arguments are set at function definition time (defining a function is an operation). Even if this can be acceptable for ShedSkin, probably it's not a good solution for a almost-system language as D. ----------------- Brad Roberts:
Converting these to helpers functions will result in a tiny little function
that will be inlined, so, right back to the same basic problem.<

I am not sure, but in Scala they can be virtual functions, so the HotSpot inlines/deinlines them dynamically and as needed to keep the code both fast and safe all the time. Thank you for all the answers, bye, bearophile
May 20 2010
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
 The nullable is done compactly, with an extra uint argument
  that encodes what arguments are actually given or what ones
 are not given and need to be initialized inside the function.

In ShedSkin things are more complex, the uint has to be a bitmask, because arguments are named and can be specified or not specified in not a fixed order. I have shown an example for the less flexible D calling syntax, where your can't ask for default arguments just in the middle of the list of arguments. Bye, bearophile
May 20 2010
prev sibling parent reply Brad Roberts <braddr slice-2.puremagic.com> writes:
On Thu, 20 May 2010, bearophile wrote:

 Brad Roberts:
 
Converting these to helpers functions will result in a tiny little function
that will be inlined, so, right back to the same basic problem.<

I am not sure, but in Scala they can be virtual functions, so the HotSpot inlines/deinlines them dynamically and as needed to keep the code both fast and safe all the time.

There's a key difference.. runtime optimization. Moving the optimizing to runtime allows the code to stay properly isolated in terms of storage and how it's bundled for shipping to end users. Because the default handling can be bundled on the 'right' side of the caller/callee barrier, it eliminates the problem of replacing the implementation with the new default and by optimizing at runtime, eliminates the overhead of the extra function call. A win win.. except for the cost of performing the optimizations at runtime and the vm in general. But that technique can't be done short of runtime optmizing, which c-family languages (such as D) don't do. So, choose your set of problems.. not really a single one that solves them all yet. Later, Brad
May 20 2010
parent bearophile <bearophileHUGS lycos.com> writes:
Brad Roberts:
 But that technique can't be done short of runtime optmizing, which 
 c-family languages (such as D) don't do.

See the future -O7 optimizatione level of LLVM/LDC I have just explained: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D.announce&article_id=18420 The future of D optimizations is not set in stone :-) Bye, bearophile
May 20 2010
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
bearophile wrote:
 D too copies the default value at the calling point. Looking at this from the
 eyes of a Python programmer this looks like a dirty hack.

I don't see how it could be called a dirty hack.
 This can be a cause of problems in D dlls too, I am not sure.

If you're designing a D dll, you have to decide what the interface actually is. Anything that appears in your .di interface file is part of the interface, and if you change that interface, you have to recompile all code that relies on it. A default argument value is part of the interface. Don't use default argument values if you don't want them to be part of the interface. They are trivially avoided: int foo(int i, int j = 7); becomes: int foo(int i); int foo(int i, int j); and in your implementation: int foo(int i) { return foo(i, y); } Again, *anything* about the interface you expose will force recompilation of everything that uses that interface, if the interface changes in any way. D provides all the features necessary to support common and well understood techniques for implementation hiding and abstraction.
May 20 2010
parent reply bearophile <bearophileHUGS lycos.com> writes:
Walter Bright:

 I don't see how it could be called a dirty hack.

Because it's untidy, in the source code it looks like the information of the default value is located in the function definition, while in truth it is stored elsewhere. This can be more efficient, but it is not tidy, this abstraction can leak a lot, you can see it here: http://d.puremagic.com/issues/show_bug.cgi?id=4028 Maybe this too is related: http://d.puremagic.com/issues/show_bug.cgi?id=3430 I presume this part of the D design will not change (even if a solution for those two bugs need to be found), but a language designer must be able to tell apart what is done for efficiency (putting the information of the default arguments at the calling point) and what's the right and tidy thing to do (putting this information somehow connected to the function itself). Bye, bearophile
May 20 2010
parent Walter Bright <newshound1 digitalmars.com> writes:
bearophile wrote:
 Walter Bright:
 
 I don't see how it could be called a dirty hack.

Because it's untidy, in the source code it looks like the information of the default value is located in the function definition, while in truth it is stored elsewhere. This can be more efficient, but it is not tidy, this abstraction can leak a lot, you can see it here: http://d.puremagic.com/issues/show_bug.cgi?id=4028 Maybe this too is related: http://d.puremagic.com/issues/show_bug.cgi?id=3430

These are both bugs, and not a design fault in having default arguments.
 I presume this part of the D design will not change (even if a solution for
 those two bugs need to be found), but a language designer must be able to
 tell apart what is done for efficiency (putting the information of the
 default arguments at the calling point) and what's the right and tidy thing
 to do (putting this information somehow connected to the function itself).

I don't agree that your proposal is necessarily right or tidy. D is a compile time language, and it would be strange to expect it to behave otherwise. I also don't see recompilation as a problem, as D compiles at an incredible speed.
May 20 2010
prev sibling next sibling parent Leandro Lucarella <llucax gmail.com> writes:
bearophile, el 20 de mayo a las 16:56 me escribiste:
 This page talks about default function argument values in C#4:
 http://www.lostechies.com/blogs/jimmy_bogard/archive/2010/05/18/caveats-of-c-4-0-optional-parameters.aspx
 
 
 Near the end it says:
 
If we change the value of the optional argument from 0 to 1, we have to
recompile all calling code for the calling code to get the updated
value! For folks shipping assemblies for a living, this means that
optional argument values don't version well, as callers have to
recompile.  When I used to work for a company whose product included
a DLL, we avoided optional method arguments for just this reason. It's
not a reason not to use optional arguments, but it's important to
understand how they work so that you don't run into headaches later.<


I think default parameters are part of the API, if you change the API, you have to recompile the client code. The only problem I see is the DLL will still run if it's not recompiled, but that can happen too with other kind of changes (like changes the enum values a function accepts), so this is not a problem present only in default values. -- Leandro Lucarella (AKA luca) http://llucax.com.ar/ ---------------------------------------------------------------------- GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05) ---------------------------------------------------------------------- In Minnesota there's a law That prevents men from having sex with living fish
May 20 2010
prev sibling next sibling parent reply Alex Makhotin <alex bitprox.com> writes:
bearophile wrote:
 Near the end it says:
 
 If we change the value of the optional argument from 0 to 1, we have to
recompile all calling code for the calling code to get the updated value! For
folks shipping assemblies for a living, this means that optional argument
values don't version well, as callers have to recompile.  When I used to work
for a company whose product included a DLL, we avoided optional method
arguments for just this reason. It's not a reason not to use optional
arguments, but it's important to understand how they work so that you don't run
into headaches later.<


Very bad idea, I would say, to change the interface after it was already published. Not surprising the new rules break the caller.
 I presume this part of the D design will not change

And again my point here(not only on this topic specifically) is that the compiler should be programmer's friend, not the opposite. It must be based on the strict specification where such possible problems documented in detail. Ideal solution, IMHO, such inconsistencies must be restricted or forbidden until documented properly. The compiler should give warnings whenever possible and as many as needed to help detect bugs. From my point of view, a good designed language should not, in principle, allow undefined, leading to bugs behavior. All possible issues should be predicted and treated adequately before they even emerge. As I look at D, I do not experience such attitude. -- Alex Makhotin, the founder of BITPROX, http://bitprox.com
May 21 2010
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Alex Makhotin:
 As I look at D, I do not experience such attitude.

D is a big step forward compared to C. Bye, bearophile
May 21 2010
prev sibling parent Kagamin <spam here.lot> writes:
Alex Makhotin Wrote:

 IMHO, such inconsistencies must be 
 restricted or forbidden until documented properly. The compiler should 
 give warnings whenever possible and as many as needed to help detect bugs.
  From my point of view, a good designed language should not, in 
 principle, allow undefined, leading to bugs behavior.

There's no language at run time, only technologies. And the compiler, being a compile-time tool, can't give warnings when the compiled application is run later.
May 21 2010
prev sibling parent Kagamin <spam here.lot> writes:
The problem you are talking of is actually a miscompilation. Why do you expect
miscompiled code to work correctly? I can think of other means to miscompile
code, so what?
May 21 2010