Hacker News new | past | comments | ask | show | jobs | submit login
Calling PHP functions with named parameters (creapptives.com)
49 points by maxpert on July 1, 2012 | hide | past | favorite | 50 comments



This is an interesting post. I'm designing a little programming language as a hobby and I am stuck at how I want to implement function calls.

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.


> It would almost be easier to have everything 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


And to go further, the network example you show (from jQuery) you could express as:

    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/


This is not criticism, but I'd just like to point out that the farther you get from the raw realities of the efficiency of C-style code you increase the probability of code becoming bloated with constructs and "rigging" under the hood that's there just to make things convenient to a programmer. At one point or another there is no escaping the reality that pointers will end-up as addresses in a register used to make an indirect call or "MOV" of some sort and that other data types will similarly land on registers or various memory areas as they are passed to and from functions. I got my start writing machine code (not assembler, but entering raw hex codes into hexadecimal keypads) and that caused me to always be very aware of where I am just being lazy and loading things-up with code just to make my life easier. Granted, sometimes this is justified.

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.


Oh, I certainly agree. I have different projects with different goals. I've spent some time eking out every ounce of performance for a Newton-Rhapson division routine in assembly. But then there are some high level things that I think lead to more elegant (and ultimately simpler) programming. Clojure for example isn't particularly close to the hardware, but it leads to better coding practices that result in easier long term maintenance, less bugs, etc.

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.


Am i missing something or does the following code not achieve the same thing?

  // define
  function blah($arr){
      extract($arr);
      // use vars here
  }
  
  // call
  blah(['foo' => 'bar', 'one' => 'two']);


TFA's code performs validation of the arguments through reflection: it ensures all required parameters are provided, and no extra parameter is. It also is a callsite interface, it's not part of the function's implementation details.


You cant ask your users (if you are designing framework) to write such a mess. Take an mvc framework for example where named url segments take place in variables automatically.


The OP solution looked messier to me honestly. We already use the pattern above in JavaScript quite successfully. I'd rather see the above implementation with a simple validator in place of extract().


Agreed on the `messier' part.

How's extract($arr, EXTR_IF_EXISTS) not validating enough?


I've always thought that named parameters, while useful as a pattern, were not entirely worthy of first class status. You can easily get the same functionality with a single associative array config parameter. That also has the additional benefit that the user of a function can reuse the config array, tweaking individual settings between multiple function calls. All in all, the amount of boilerplate you save by making named parameters first class is tiny (unless, of course, your language has some kind of preposterously verbose associative array constructor syntax, like PHP does).


> You can easily get the same functionality with a single associative array config parameter.

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.


What? The benefit is not saved lines, it's ease of expressing explicitness (ie: inline with the function call instead of throw assigning to variables above it). 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).


> What? The benefit is not saved lines

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.


> Not really, you lose most of the validation and documentation...

This is only true if you don't replace these forms of documentation with something better, which I'd do.


* Validation is not documentation, either it's there or it's not there, with built-in keyword arguments it's there, with a hash-hack it probably isn't.

* 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.


You mean 5.4's associative array syntax:

func([$arg1,'name2'=>$arg2,$arg3]);

is too verbose for you for a mix of named and non-named parameters? How would you shorten it?


Oh, yes 5.4 is a bit improved, but how would I shorten it? I'd drop the quotes around 'name2' whenever it was a legal identifier, and I'd ditch the hideous => symbol.


I think dropping the quotes is a bad idea. This isn't javascript. However, I do agree about the => symbol. I would have preferred if in 5.4 they added support for ':' so, ['a':$b]. At which point, I'm sure someone will say that looks so similar to js syntax, why not drop the quotes? To which I say -personally- I just think it leads to more confusion. (Although for some reason I don't mind that in js - I know.. weird preferences.)


>I think dropping the quotes is a bad idea. This isn't javascript.

That doesn't sound like a legitimate reason to me. It's not javascript. So what?


It's much improved in 5.4. I think the grandparent post's point was that prior to 5.4, this would've looked like

func(array("arg1", "name2"=>"arg2", "arg3"));

Which is much "uglier" than the new syntax.


>unless, of course, your language has some kind of preposterously verbose associative array constructor syntax, like PHP does

array( => , => , ...) is "preposterously verbose?

You should see some other languages for the "preposterously" part.

Plus 5.4 has short array syntax.


Or use Python?


Seriously. Implementing your own language features like this is a MUCH MUCH bigger kludge than just using your language as-designed. Code like this is so much harder to reason about and read, and so much more complicated.

If 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.


WordPress has a similar approach. The wp_parse_args function which accept an array or a URL-like string. You can also pass default values (and extract() them if you want.)

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.


Wordpress should upgrade wp_parse_args to use this Gist.


The nice thing about named parameters in Python is that they're optional, and aren't all or nothing.

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.


The example explicitly shows that optional parameters are not required:

   if (!$p->isOptional() and !isset($arr[$p->name])) throw new MissingArgumentException


This doesn't actually save anytime or space, and just adds a confusing layer. If you have to explicitly define a map of the name of the argument to the value of it, it's not significantly shorter than just assigning the value to the name of a variable and passing that in, ie:

$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)
);


I'm often using compact()/extract() in similar, but not necessarily identical pattern. For example:

  $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)


Why? This is a horrible idea. It makes your code so much harder to read or reason about, because it's impossible to tell what variables will be passed in without looking at every single callsite.


That's not how the code works; I can assure you after using it for some time. The client code (the callee) is in control of variables and lists explicitly what will be provided, either as default or as an actual (keyword) argument.

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.


Performance note - having call_* functions in your code guarantees that your work will not opcode cache friendly - WP is guilty of this (in addition to their include/require spaghetti).


I'd suggest adding "take this regular function and return function callable with array" function: https://gist.github.com/3029279

(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)


Added


Cool, but if you're using it in a production code do make sure to link this blog post as a comment. You don't want some poor developer who takes over your project scratching his head.

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.


You can do the following in PHP:

  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.


That doesn't seem "right" to me, though. What if you have a variable in your calling scope that conflicts with a parameter in your function?


That's an interesting way to express this!


This will kill your performance... function calls are slow, but calling them via call_user_func is crazy slow.


It's pretty negligible in the scheme of things. I was abusing PHP reflection a while back to add decorator support (https://github.com/chriso/Request) and used this pattern to reduce the cost:

    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;
    }


Why not something like?

    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.


i'd advise the OP to pass in an array. and look into an API-inspired pattern which organizes code and above all, proper type-chcking and error handling.

for instance:

$result = App:api('/do/something', array(name=>'john', age=>10, etc));

class Do extends Api { function something($params = array('name'=>'', age=>'')){ ... } }


Besides the ugly syntax look... Awesome feature!


The result is some very sad looking code.


Sad in what sense?


I think he means sad in the sense that this should not be in application code. It should be in PHP interpreter. Fixing the interpreter in application code. Sad.


Sad is definitely overkill here, especially for such a short snippet, but personally I prefer avoiding nested conditionals and else statements in general. Here's a fork that keeps conditional clauses to one line, and I personally find much more readable - https://gist.github.com/3029246

(disclaimer- not tested at all)


Thanks for forking and keep improving, I wish PHP guys do it at a native level too.


In the same sense as code that uses call_user_func_array is dirty looking. It's cruft that has nothing to do with application logic. It'd be nice to be able to do away with these things.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: