Calling PHP functions with named parameters
blog.creapptives.com
blog.creapptives.com
Without named parameters, you end up with something like the Win32API:
ReadSomeFile("file.txt", SOME_FLAG, 0, SOME_OTHER_FLAG, NULL, NULL, CREATE_FILE);
This can largely be avoided by structuring your API into little single-purpose chunks, but it remains a bit of a problem.Also I think it's somewhat annoying that you have to remember the order of arguments. In my opinion, this order should not be significant. It leads to a lot of bugs (for people who don't check the documentation for every method) with things like:
str_replace(needle, haystack)
vs str_replace(haystack, needle)
It would almost be easier to have everything take a map ajax
url: "example.com/newwidget"
method: "post"
error: -> ...
success: -> ...
But if you enforce this for everything you get something like: square
number: 4
which is just ridiculous. I kind of like Python's way of doing it -- making them optional.One thing's for certain -- Javascript frameworks need to develop some kind of consistency. Sometimes you pass in parameters, and sometimes you pass in a configuration object. This inconsistency makes it difficult, and it's too hard to remember which calls take parameters and which take a map.
Smalltalk and Self have demonstrated that it is, indeed, rather easy. And at this point you can just remove the separation between "keyword arguments" and "method name", they are one and the same.
> But if you enforce this for everything you get something like:
Well you can still have operators:
4**2
or you can use a message to numbers: 4 raisedTo: 2
even a unary one specifically for this operation: 4 square
or you can have `square` be a message to some sort of math namespaces: Math square: 4 Request to: url withMethod: method onSuccess: success onError: error
with various overloads for optional component (the minimally complete expression being `Request to: url`)but various Smalltalk distributions & libraries have various ways to do this (usually synchronous so it's not a precise mapping). Squeak's WebClient[0] would be something like that:
body := (WebClient httpGet: url) content.
[0] http://www.squeaksource.com/WebClient/Today's coding tools (editors and IDE's with code completion and "live" API reference) make it harder to screw some of this up. I tend to always go for simple, fast and efficient. But that's my preference.
I think a good balance would be to take a well designed language and add an option for high-performance code when needed. C and C++ do this with inline assembly (although I don't know that anyone would call C "elegant"). It would be nice if more higher-level languages added this ability.
// define
function blah($arr){
extract($arr);
// use vars here
}
// call
blah(['foo' => 'bar', 'one' => 'two']);How's extract($arr, EXTR_IF_EXISTS) not validating enough?
Not really, you lose most of the validation and documentation, you get 2 levels of parameters (first-class positionals and second-class keywords), you may not correctly detect or handle redundant provisions, defaults may or may not work the same way (and correctly), and it's not possible to provide through keywords a parameter the function's implementer has defined as positional, even if that's clearer for the callsite.
It works-ish, but it is and remains a hack and a pale shadow of first-class keyword parameters.
On the other hand, I can get behind the inverse: keyword-only parameters and positionals by abusing arrays (à la Smalltalk, Self or Objective-C), in my experience that significantly increases the code's readability and flexibility. Though it hardly plays nicely with partial application.
> That also has the additional benefit that the user of a function can reuse the config array
parameter packing & unpacking takes care of that, it's not an advantage of "a single associative array".
> the amount of boilerplate you save by making named parameters first class is tiny
To get the same functionality as e.g. Python's kwargs, we're probably talking about half a dozen lines. In each function. That's not tiny.
Not sure what you're talking about. When somebody talks about "boilerplate", it's very much about the number of (setup/redundant) expressions/tokens having to be used to do something.
> Please show an example of how this is saving lines in a way that's not comprable to simply doing multiple assigns on one line (ie: destroying readability).
I would suggest actually reading my comment, that may give you a hint as to what I'm comparing and let you understand on your own how different it is.
This is only true if you don't replace these forms of documentation with something better, which I'd do.
* Documenting a hash-hack will still require spelling out that one of the positional parameters is a hash, then diving into that hash to spell out the various keys. This adds one worthless level of indirections to first-class keyword parameters, which can just treat all parameters the same way for documentary purposes.
func([$arg1,'name2'=>$arg2,$arg3]);
is too verbose for you for a mix of named and non-named parameters? How would you shorten it?
func(array("arg1", "name2"=>"arg2", "arg3"));
Which is much "uglier" than the new syntax.
That doesn't sound like a legitimate reason to me. It's not javascript. So what?
array( => , => , ...) is "preposterously verbose?
You should see some other languages for the "preposterously" part.
Plus 5.4 has short array syntax.
http://codex.wordpress.org/Function_Reference/wp_parse_args
You can pass the arguments as an array or as a URL-like ampersand separed string.
From what I can tell, with this (and other similar hacks I've seen), you can either provide no named parameters, as usual, or you have to name every argument passed, in an array.
In such a case, I fail to see how implementing this can do anything but increase code complexity.
if (!$p->isOptional() and !isset($arr[$p->name])) throw new MissingArgumentExceptionIf you really feel you can't program effectively in a language that lacks keyword arguments, then for christ's sake, use a language with keyword arguments.
$some_user_flag = true;
$some_other_flag = false;
$user->someFunction($some_user_flag, $some_other_flag);
vs. (assuming $user->someFunction is now your already defined/returned function because this has been abstracted away in your stack somehow):
$user->someFunction(array(
'some_user_flag' => true,
'some_other_flag' => false)
); $foo = get_some_value();
$bar = get_other_value();
my_function(compact('foo', 'bar'));
function my_function($args) {
# optionally provide default values :-)
$foo = default_value;
$bar = default_value;
extract($args, EXTR_IF_EXISTS); # the EXTR_IF_EXISTS is optional, for when we want to enforce sanity check
/* function code goes here */
}
(edits: clarifications)When using EXTR_IF_EXISTS and list of default values, you know exactly what variables you will get -- either passed as argument, or by default, by reading the callee.
When not using EXTR_IF_EXISTS and you want to extract only known-good variables from untrusted input, array_intersect_key() helps.
The idiom may seem strange at first, but once you get used to it, you read it naturally. That's the thing with idioms, after all. Point in case, our new hire got up to speed very quickly.
----
EDIT: in some cases I just skip the extract()ing and simply access $args['field_name']. It's safe as PHP will raise error, stop execution and drop to error handler if some field was not provided by mistake -- or malicious user action. And any superfluous fields remain unaccessed, safe again.
(also, this variant is better because you can mitigate performance losses by moving all reflection operations out of lambda function to its parent, make_named_array_function)
P.S. Sometimes that poor developer can you be too ;) Don't know how many times what seemed to be a great idea at a moment to me, leads to the biggest wtf after a month.
some_function($param='hello', $somthingelse=3);
It's not quite as good as the method in this post, as you still have to specify params in the correct order, but it means at a glance it's easier to remember what each parameter is. In fach I do this a lot for that very reason. switch (count($args)) {
case 0: $method(); break;
case 1: $method($args[0]); break;
case 2: $method($args[0], $args[1]); break;
//etc
default: call_user_func_array($method, $args); break;
} function myfunc($kwargs) {
extract($kwargs);
}
And then just call it with an array. Sure, it's ugly, but that's the price of using PHP.for instance:
$result = App:api('/do/something', array(name=>'john', age=>10, etc));
class Do extends Api { function something($params = array('name'=>'', age=>'')){ ... } }
(disclaimer- not tested at all)