Post #113,678
8/13/03 10:19:59 AM
|
Positional parameters vs. Named parameters.
Had a short discussion today with my programming colleagues about designing functions in an API to take positional parameters or named parameters. I think I can see pros and cons to both approaches, but I'd like to put them through the IGM and see what falls out.
We are coding an intranet application. There is a fairly powerful framework which has been developed along with the application (there is considerable intertwining, actually) and thus an API layer for both common things and database objects. The problem really began many months ago when some of the more versatile functions began to require more and more parameters to control their behaviour. Unfortunately, the language is PHP which natively supports only positional parameters, like most languages. Why is this a problem? Well, for many of these situations, the extra parameters are not used all that frequently and were often added to meet a need in a page. Calling such functions often results in needlessly specified parameters to get to the ones that really matter for this call.
I came up with a solution of a sorts when this began to be annoying. The solution was to pass named array parameters in a single argument that expects that. This works uniquely well in PHP because PHP does not distinguish between a list and a hash-table: PHP arrays always simultaneously function as both. There are now a number of functions that use this mechanism.
There are several advantages to this approach: the calling code is reasonably descriptive as to what it's passing to the function. Self-commenting, to an extent, and easier to maintain. It is also much easier to provide only the values that are non-default, without needing to re-specify essentially default values. The downside is that the function itself must be carefully commented as to what parameters it is looking for and what they do. A toolbox "setup default" function helps a little, as well as intializing defaults for unprovided parameters the options array. The other downside is that the calling code has no information about the omitted default parameters. So far the best way to combat that is to have obvious and sensible defaults (which can be harder than it sounds).
The traditional approach of purely positional parameters has the advantage of the function definition listing meaningful names (theroetically) for each parameter. However, the calling code becomes harder to read and understand. This is exacerbated when the first few parameters are clearly meaningful data, then there is a number of nulls, empty strings and zeros ending in an obviously unique parameter that provides few clues as to what it is for. This problem is particularly apparent when a number of the dozen parameters are merely flags, or some number of unknown units - this was one reason I went looking for something better, BTW.
Now, one of my colleagues said he doesn't like creating hash-tables on the fly merely for passing named parameters into functions, though he couldn't explain why this was a problem except in purely academic terms (e.g. "that's not what hashes are for"). His solution was to create more functions that exposed or assumed common combinations of available parameters. This, of course, creates more functions in the name-space, when adding options to an existing function often enables another virtually identical one to be removed (or not written) - this is the other reason I found a way to do named parameters. To be fair, he proposed wrapper functions, but I'm not keen on the namespace pollution. His reasoning for this solution was also along academic lines, but I might be mis-remembering his comments.
I know I tend to play fast-and-loose with so-called "right ways" to do things in programming. I've lost count of the things I've made PHP do that has startled a colleague (it's not that high a number, but I haven't been keeping count and looking back I wish I had).
Am I encountering someone who'd rather be a bit too safe? Or am I dicing with something a little too unconventional? What do people think about using PHP's arrays in this fashion? Is synthesizing named parameters worth the hassle, or should I go back to positional parameters?
Wade.
Is it enough to love Is it enough to breathe Somebody rip my heart out And leave me here to bleed
| | Is it enough to die Somebody save my life I'd rather be Anything but Ordinary Please
| -- "Anything but Ordinary" by Avril Lavigne. |
|
Post #113,679
8/13/03 10:28:41 AM
|
Is it possible to ...
pass the name of the parameter and the value?
I have fought the battle of passing positional parameters, and it's a pain when, as you stated, there are a lot of parameters and you only need one at the "end".
In Access I can either pass positional parameters or the Parameter name and the value (made up example: PageLen:60). That gives me the ability to pass all the parameters positionally, or just the one(s) needed.
|
Post #113,683
8/13/03 10:53:14 AM
|
Not sure i'd use a dictionary
I'm prone to believing that you shouldn't try to fight the capabilities of a language. I'd think that creating hashes on the fly would be somewhat expensive, though as you know, sending a ton of parameters over is also expensive.
You mention creating wrapper functions but does PHP support function overloading - i.e. the function is named the same but it has variants that take different types and numbers of parameters. If so, at least you avoid the problem of namespace pollution.
The other alternative is to use objects to encapsulate the logic - instead of the swiss army knife of dictionaries. Create a class that has all the various parameters sert to defaults. Set up some getter/setter functions to allow you to alter the defaults. Pass the object to the function. Or better yet, have the function be an encapsulated method of the object.
|
Post #113,685
8/13/03 11:14:25 AM
|
That last suggestion...
Huge parameter lists are a good sign of an API gone bad. The functions are *too* versatile.
Regards,
-scott anderson
"Welcome to Rivendell, Mr. Anderson..."
|
Post #113,687
8/13/03 11:27:46 AM
|
Problem with overloading
Say you have a function with three required parameters and three optional ones. You call it three times, each with a different one of the optional parameters. Each time you have four parameters, and positional doesn't work to identify the fourth. I wrestled with the same thing as Wade, and came up with a hybrid: positional for the required parameters, and an array at the end for optinal ones. eg: function clsDB( $db_server, $db_name, $options ){\n if( $options['debug_level'] > 0 ){\n $debug_level = $options['debug_level'];\n }\n if( $options['test_mode'] > 0 ){\n $test_mode = $options['debug_level'];\n }\n etc ...\n}
===
Implicitly condoning stupidity since 2001.
|
Post #113,770
8/14/03 12:00:27 AM
|
To be honest, I don't know.
But it's unlikely that ad-hoc hashes are expensive in PHP. I believe this because lists and hash-tables are the same thing in PHP. If the types were separate, like they are in practically every other language, then, yes, I might agree with you.
PHP doesn't have function overloading. There is some experimental support for objects, but I've not used it and the documentation isn't clear.
Using an object would work for some of those functions. It's true I hadn't thought of that but I don't automatically think in objects. The downside is that it would make the calling code more verbose. This could probably be mitigated somewhat with some sub-classes and clever defaults, though.
Wade.
Is it enough to love Is it enough to breathe Somebody rip my heart out And leave me here to bleed
| | Is it enough to die Somebody save my life I'd rather be Anything but Ordinary Please
| -- "Anything but Ordinary" by Avril Lavigne. |
|
Post #113,775
8/14/03 2:45:29 AM
|
Python cookbook example + discussion:
[link|http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68424|http://aspn.activest...thon/Recipe/68424]
"There's a set of rules that anything that was in the world when you were born is normal and natural. Anything invented between when you were 15 and 35 is new and revolutionary and exciting, and you'll probably get a career in it. Anything invented after you're 35 is against the natural order of things." Douglas Adams
|
Post #113,776
8/14/03 2:51:47 AM
|
Interesting. Some good ideas there.
Is it enough to love Is it enough to breathe Somebody rip my heart out And leave me here to bleed
| | Is it enough to die Somebody save my life I'd rather be Anything but Ordinary Please
| -- "Anything but Ordinary" by Avril Lavigne. |
|
Post #113,691
8/13/03 11:48:39 AM
|
A technique
First off - I'm a fan of named parameters - which is why I much prefer languages like ObjectiveC and Smalltalk as they name the params in the message names.
dict at: key put: anObject.
I name my java method names similarly
map.atKeyPutObject(key,object);
Yes they get looooong - but they are understandable.
longest method name I remember in the WebObjects api is
object addObject: anObject toBothSidesOfRelationshipWithKey: relationshipName;
Lately I've been running into situations where I've written a method and it does exactly what I want in 90% lf cases and I'm using it all over the code. Then I hit a case where it has to act just a tiny bit different.
So I add an additional parameter and extend the name of the original function, then reimplement the function to pass what would be the default parameter.
so I had product.addDemand(demandQuantity) throws UnsatisfiableDemandException
and then well into the project I learn that there is a case where you have to push the demand through regardless of whether you can satisfy it - which ended up with me changing the first method to:
product.addDemandForced(demandQuantity, shouldForce)
and creating
product.addDemand(demandQuantity) { addDemandForced(demandQuantity,false); }
Still clear what's going on and I don't have to touch the already written code that uses the usual behavior.
Smalltalk is dangerous. It is a drug. My advice to you would be don't try it; it could ruin your life. Once you take the time to learn it (to REALLY learn it) you will see that there is nothing out there (yet) to touch it. Of course, like all drugs, how dangerous it is depends on your character. It may be that once you've got to this stage you'll find it difficult (if not impossible) to "go back" to other languages and, if you are forced to, you might become an embittered character constantly muttering ascerbic comments under your breath. Who knows, you may even have to quit the software industry altogether because nothing else lives up to your new expectations. --AndyBower
|
Post #113,772
8/14/03 12:33:13 AM
|
That's pretty much what I'm trying to do.
Except that PHP doesn't natively support named paramaters - hence my trick.
The other thing I found is that after adding some option that that 10% needed, I would often find the function would now be useful in dozens more places and that that 10% was now 30% or more. Then I'd find another option needed and it would be even more useful. Until the original variant is now only being used 25% of the time and the latest option is being used 60%... :-)
Wade.
Is it enough to love Is it enough to breathe Somebody rip my heart out And leave me here to bleed
| | Is it enough to die Somebody save my life I'd rather be Anything but Ordinary Please
| -- "Anything but Ordinary" by Avril Lavigne. |
|
Post #113,820
8/14/03 2:28:14 PM
|
Best Advice: Be merciless in your refactoring.
Often when that happens - the function in question really wants to be more functions thatt are called in various combinations.
Smalltalk is dangerous. It is a drug. My advice to you would be don't try it; it could ruin your life. Once you take the time to learn it (to REALLY learn it) you will see that there is nothing out there (yet) to touch it. Of course, like all drugs, how dangerous it is depends on your character. It may be that once you've got to this stage you'll find it difficult (if not impossible) to "go back" to other languages and, if you are forced to, you might become an embittered character constantly muttering ascerbic comments under your breath. Who knows, you may even have to quit the software industry altogether because nothing else lives up to your new expectations. --AndyBower
|
Post #113,822
8/14/03 2:36:13 PM
|
Re: Best Advice: Be merciless in your refactoring.
How do you decide this?
In math, one would often have a situation in which an "abstract language concept" - say templates in C++ - would present itself. Now, you see how IF IT REALLY WORKED this would be nice - I've made up an inner-product class and the spaces be damned! - but in the end, I would always opt for specific coding for speed and efficiency and ease of later use. This always works better - meaning - templates are not of any real use for an actual problem, because you have to also maintain the template.
-drl
|
Post #113,884
8/15/03 1:54:20 PM
|
Its like cleaning house
You get a feel for things after awhile - but basically anytime you spot a chunk bigger than 2 lines long of nearly identical code, its time for a new function.
The other metric is that of a "thought".
Each thought should be a function/method/reusable thing.
The more advanced a program gets - the smaller it gets.
Smalltalk is dangerous. It is a drug. My advice to you would be don't try it; it could ruin your life. Once you take the time to learn it (to REALLY learn it) you will see that there is nothing out there (yet) to touch it. Of course, like all drugs, how dangerous it is depends on your character. It may be that once you've got to this stage you'll find it difficult (if not impossible) to "go back" to other languages and, if you are forced to, you might become an embittered character constantly muttering ascerbic comments under your breath. Who knows, you may even have to quit the software industry altogether because nothing else lives up to your new expectations. --AndyBower
|
Post #113,706
8/13/03 2:33:49 PM
|
May be you have a class trying to get born
Something along the lines of
doer = new doer; doer->setP1(value for defaulted P1); doer->do(must-have parameters);
For simple cases,
new Doer() -> do(...);
or even
Doer::do(...)
--
Less Is More. In my book, About Face, I introduce over 50 powerful design axioms. This is one of them.
--Alan Cooper. The Inmates Are Running the Asylum
|
Post #113,713
8/13/03 3:22:19 PM
|
Can't say without code
Without seeing the code that is causing the problems I can't say which solution I would consider better.
Functions with lots of paramters can be a sign that you need to clean up the code. Sometimes the solution is to break up the function into multiple functions, other times just reworking the interface can fix the problem. Wrapper functions can be the best solution in some cases, the most obvious is when a function has mutually exclusive parameters.
Ultimatly, your having a discussion about which method of coding is cleaner. Is it better to create a lot of different functions or a small number of functions with complex parameter lists?
Jay
|
Post #113,771
8/14/03 12:26:08 AM
|
Define 'better' :-) [+ some examples]
The former method is like the following:
input_select('d_name', null, null, $name_list, 30, 'selectName', false, true);
The latter method is like the following:
input_control(INPUT_SELECT, 'd_name', null, array('list' => $name_list, 'changehook' => 'selectName', 'multiple' => true));
In my experience in this application, I find the latter to be far preferable.
Actually, that's not a wonderfully good example. We had a series of functions with different argument orders for creating various HTML form controls. Most of them suffered from the option-itis we've been discussing, and there were many features most of them could share but did not because it would involve copying code. So I created a single function that could draw any form control, given the right combination of parameters. The first three were the type, it's name and it's value. The remainder were provided in an array. The biggest advantage this gave me was that now all types support the same variety of parameters, including several that are available because they are all HTML tags.
An object-tree might work for this API, though it would probably involve replacing lots of single calls with three or four lines of code. :-/
Wrapper functions could work, too, though there are still four or five parameters that would be optional for all variants. Hmm.
Wade.
Is it enough to love Is it enough to breathe Somebody rip my heart out And leave me here to bleed
| | Is it enough to die Somebody save my life I'd rather be Anything but Ordinary Please
| -- "Anything but Ordinary" by Avril Lavigne. |
|
Post #113,824
8/14/03 3:49:31 PM
|
Re: Define 'better' :-) [+ some examples]
I spent a lot of time wrestling with the question of how to design a good interface for writing HTML myself. I was doing it in VB however, and what works well in one language doesn't always work well in another. And in any case, I was never able to find a totally satisfying solution. I tried a couple of different styles of function interfaces. I also futzed around with object oriented solutions, which I selved more because I wasn't in a posistion to suggest such a radical change in a system that was otherwise totally procedural. I ended up going with a large library of functions. It was ultimatly a question of it aesthetically looking better to me then putting it all in one big function and using lots of named parameters.* However, if I had to do it again, I think I would try to split the difference. Have a seperate function for each of the types of HTML controls, but have named parameters tacked on the end for the rarely used options, plus various wrapper functions that cover the most commonly used specific controls that do nothing but setup parameters and coll the underlying generic function. The interface would end up looking something like this HSelect (Name, Size, manditory parameters, optional parameters)\n HDateSelect (Name, StartDate, EndDate, optional parameters)\n HStateSelect (Name, optional parameters)\nHInput (Name, Size, manditory parameters, optional parameters)\n HDateInput (Name, valid date range, optional parameters)\n HSSNInput (Name, optional parameters) HDateSelect and HStateSelect would just convert their inputs into the generic data taken by HSelect and then call that function. However, from what I read in your post, I suspect you lean heavily the other way. You would rather have HControl (Type, Name, Size, manditory parameters, optional parameters). Jay * Technically speaking, named parameters and optional parameters are two different things in VB. But in practice, the only reason to use named parameters is that they are cleanest way to do optional parameters.
|
Post #113,972
8/16/03 4:05:07 AM
8/16/03 4:08:51 AM
|
another solution
I have kicked around the idea of simulated named parameters using a string. Example:
href('Forums', 'z.iwethey.org', ''); // no extra params
href('Forums', 'z.iwethey.org', 'underline=no, target="_blank"');
Inside we could then have a standard function(s) that parses out the value:
function href($title, $url, $namedParams) { .... $target = getParam($namedParams, 'target'); if (! empty($target)) {....} .... } // end-func
However, the parsing may slow things down. But for typical intranets it may be fine.
________________ oop.ismad.com
Edited by tablizer
Aug. 16, 2003, 04:06:45 AM EDT
Edited by tablizer
Aug. 16, 2003, 04:08:51 AM EDT
|
Post #113,981
8/16/03 7:53:22 AM
|
No, it's not.
In fact, it's slightly worse. The string parsing required would be a significant drawback; in ease-of-use, in efficiency and in concept. FWIW, the PHP equivalent in my approach to that would be:
href('Forums', 'z.iwethey.org', array('underline' => false, 'target' => "_blank");
Wade.
Is it enough to love Is it enough to breathe Somebody rip my heart out And leave me here to bleed
| | Is it enough to die Somebody save my life I'd rather be Anything but Ordinary Please
| -- "Anything but Ordinary" by Avril Lavigne. |
|
Post #114,452
8/19/03 11:49:09 PM
|
That is more syntax
My approach is only 2 more characters than native named params. Yours is at least a dozen more. Slower? Yes, I agree. But it depends on the environment whether speed is the bottleneck or harder-to-read code.
________________ oop.ismad.com
|
Post #114,454
8/19/03 11:50:34 PM
|
OT: Bryce...
Send me your information... I got the BOOK!
-- [link|mailto:greg@gregfolkert.net|greg], [link|http://www.iwethey.org/ed_curry|REMEMBER ED CURRY!] @ iwethey
[insert witty saying here]
|
Post #115,176
8/24/03 2:03:40 PM
8/24/03 2:06:08 PM
|
OT: I don't have access to receipt. On biz travel
Okay with me to assume stamped postage value.
________________ oop.ismad.com
Edited by tablizer
Aug. 24, 2003, 02:06:08 PM EDT
|
Post #114,573
8/20/03 1:30:02 PM
|
Also more limited
The string approach is also limited to literal values. Variables would have to be interpolated into the string (easy or not, depending on the language), but even then would only work with values that can be reliably converted to a string and back.
-- -- Jim Weirich jweirich@one.net [link|http://onestepback.org|http://onestepback.org] --------------------------------------------------------------------- "Beware of bugs in the above code; I have only proved it correct, not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)
|
Post #114,590
8/20/03 2:40:14 PM
|
There be XML
--
Less Is More. In my book, About Face, I introduce over 50 powerful design axioms. This is one of them.
--Alan Cooper. The Inmates Are Running the Asylum
|
Post #115,179
8/24/03 2:13:45 PM
|
but XML "solves" the problem by not having variables
________________ oop.ismad.com
|
Post #115,178
8/24/03 2:12:53 PM
8/24/03 2:15:27 PM
|
minor issues IMO
The string approach is also limited to literal values. Variables would have to be interpolated into the string (easy or not, depending on the language)
PHP makes variable insertion easy. Example:
$bar = "glog"; foo($x, $y, $z, "myvar=$bar");
Of course, if you are passing something more complex than a variable, this may not be so easy. But, if you are passing an array, then a dedicated parameter is probably the way to go. Or, just pass SQL needed to retreive the info.
but even then would only work with values that can be reliably converted to a string and back.
Being that I tend to put complex structures in the database instead of code thingies, this is usually not a problem for me.
________________ oop.ismad.com
Edited by tablizer
Aug. 24, 2003, 02:15:27 PM EDT
|
Post #115,262
8/25/03 5:58:10 AM
8/25/03 6:02:24 AM
|
I should have known.
This is not actually for your benefit, Bryce, but it looks to me that you just didn't get my point about how I've implemented named parameters. I'm not surprised, really.
Wade.
P.S. Oh yeah: don't take this the wrong way! :-) It's just that by this point in a conversation, experience has taught me that if you haven't gotten the original point, it's a waste of time trying to get you to see it.
Is it enough to love Is it enough to breathe Somebody rip my heart out And leave me here to bleed
| | Is it enough to die Somebody save my life I'd rather be Anything but Ordinary Please
| -- "Anything but Ordinary" by Avril Lavigne. |
Edited by static
Aug. 25, 2003, 06:02:24 AM EDT
|
Post #115,326
8/25/03 4:54:32 PM
|
Re: minor issues IMO
Jim: but even then would only work with values that can be reliably converted to a string and back.
Tablizer: Being that I tend to put complex structures in the database instead of code thingies, this is usually not a problem for me.
Things that cannot be converted to a string and back are usually the types of things that cannot be stored in a database. I'm thinking of open file connections, database connections, connection pools, procedure objects, semaphores, sockets, threads, mutexs, execution contexts, continuations, etc.
All of the above could easily be passed in an array or hash.
-- -- Jim Weirich jweirich@one.net [link|http://onestepback.org|http://onestepback.org] --------------------------------------------------------------------- "Beware of bugs in the above code; I have only proved it correct, not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)
|
Post #115,511
8/27/03 2:31:45 AM
|
difference in style
Things that cannot be converted to a string and back are usually the types of things that cannot be stored in a database. I'm thinking of open file connections, database connections, connection pools, procedure objects, semaphores, sockets, threads, mutexs, execution contexts, continuations, etc.
I suppose it depends on how the language works. In some langs/libs things like file connections and database connections were simply integers (ID's). The language kept a list (table?) of open "things". If there was a crash or shut-down, the list would be traversed and items closed.
I suppose in my ideal language and environment, EVERYTHING would be a table (surprise surprise). Thus, "complex structures" would be things like queries (strings) or ID's. Generally you reference the complex thing or service rather than pass it around as a RAM lump.
But tablisms aside, I don't generally find a need to put those kinds of things in named parameters for some reason. Named parameters are generally for "tweaks" rather than some complex structure by itself. I agree that arrays are more flexible, but like I said before, they are more wordy in calls. Given the tradeoffs, I would take less code. The few things that need complex RAM thingies down the road simply get a new fixed-position parameter, even if it requires finding and fixing existing calls. I doubt it would happen enough to justify more syntax. That is my weigh-in. Take it or leave it. (Assuming lack of real named params.)
________________ oop.ismad.com
|
Post #118,384
9/21/03 6:13:56 PM
|
I generally lean towards the hash solution
I would also suggest that you keep track of which keys you looked at, and after you are done argument processing throw some sort of warning or error (depending on the design of your project) if any named parameters were not used.
My experience suggests that the unused named parameters are indications of a programmer mistyping the name, and that giving feedback on that as fast as possible will speed up development.
Cheers, Ben
"good ideas and bad code build communities, the other three combinations do not" - [link|http://archives.real-time.com/pipermail/cocoon-devel/2000-October/003023.html|Stefano Mazzocchi]
|
Post #118,682
9/24/03 8:30:32 AM
|
That's a good idea.
And one we didn't think of! :-) Thanks.
Wade.
Is it enough to love Is it enough to breathe Somebody rip my heart out And leave me here to bleed
| | Is it enough to die Somebody save my life I'd rather be Anything but Ordinary Please
| -- "Anything but Ordinary" by Avril Lavigne. |
|