Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In depth Closure definitions. #830

Open
eborden opened this issue May 23, 2013 · 29 comments
Open

In depth Closure definitions. #830

eborden opened this issue May 23, 2013 · 29 comments

Comments

@eborden
Copy link

eborden commented May 23, 2013

Doc blocks do not currently support in depth Closure definitions. Only a shallow definition is possible with the type definition:

/*
 * @return Closure [<description>]
 */

Use of closures is becoming more prevalent in modern PHP, so a more in depth format is needed.

Some possible formats

Single line definition. Double brackets are used to denote a sub definition.

/* 
 * @return Closure [[@param [type] [name] [<description>]; @return [type] [<description>]; ...]]
 */

Inner doc block definition. Ending comment statement is excluded on the inner doc block.

/*
 * @return Closure /* @param [type] [name] [<description>]
 *                  * @return [type] [<description>]
 *                  * ...
 */
@mvriel
Copy link
Member

mvriel commented May 23, 2013

Hmm.. interesting.

We have been talking about a concept know as Inline DocBlocks or defining inline types using an @struct. Perhaps we can re-use this concept for consistency. I'll try to elaborate more once I have time

@eborden
Copy link
Author

eborden commented May 23, 2013

Yes whatever comes out of this could be used for more than just Closure definitions, but any open ended type that is being used in a structured manner. Looking forward to your elaboration.

@pavelkouril
Copy link

What about something like this?

@param Func<string, string, string> $concat String concatenator

@param Action $logger Message logger

Action would be a closure that would accept every given type as input parameter and return nothing and function would accept all the given types (sans the last one) as input and would return the last type given.

The syntax is taken from C#, where it is really popular and useful thing: http://simpleprogrammer.com/2010/09/24/explaining-what-action-and-func-are/

@mvriel
Copy link
Member

mvriel commented Jul 19, 2013

I am afraid that Func and Action do not cover the requested functionality since your are not able to define which parameters and return value is associated with the Closure.

One of the options that I am thinking of is to be able to declare Closures in a PHPDoc comment.

For example:

/**
 * @declare function myClosure {
 *     @param string $firstArgument
 *     @param string $secondArgument
 *     @return string
 * }
 * 
 * @return callable<myClosure>
 */
public function test()
{
}

This is just an example and even the tag name could very well change. This will allow many more things such as declaring a stdClass substitution in case of SOAP, struct definitions for associative arrays and alike.

Obviously this is a big feature and more research time is needed; it will be after 2.0 that I will have a lot of time to investigate this deeper

@eborden
Copy link
Author

eborden commented Jul 19, 2013

Block syntax {...} does seem to make sense. However the declaration seems awkward with the unnamed nature of closures and anonymous functions. Why not the following:

/**
 * @return callable {
 *     @param string $firstArgument
 *     @param string $secondArgument
 *     @return string
 * }
 */

This could even be shortened to an inline in the case of closures that do not take arguments.

/**
 * @return callable {@return string}
 */

And would read fluidly in the case of curried functions

/**
 * @return callable {
 *     @param string $firstArgument
 *     @param string $secondArgument
 *     @return {
 *               ...
 *             }
 * } description of the callable.
 */

The one down side of this method is that descriptions then become awkward.

/**
 * @return callable {
 *     @param string $firstArgument
 *     @param string $secondArgument
 *     @return string
 * } description of the callable.
 */

Declaration format has the benefit of allowing a less awkward description:

 * @return callable<myClosure> description of my callable

But then you are getting in to repetition of the description. The description should really be bound to the declaration, but again we run in to an awkward syntax.

@boenrobot
Copy link
Contributor

Nah. The description of a returned value belongs at the method's return tag. Declarations are no different, assuming you declare them just once in the file's docblock.

Whether we allow "inline" declarations is debatable, but we MUST have non-inline declarations at minimum, as this is the only way to make circular returns.

(Side note, we've already talked about inline docblocks... we may have to leverage that syntax if there's going to be tags inside tags)

e.g.

/**
 * @declare function forward {{
 *     @param int $speed
 *     @param string $text
 *     @return callable<back> 
 * }}
 * @declare function back {{
 *     @param int $speed
 *     @param string $text
 *     @return callable<forward>
 * }}
 */

/**
 * 
 * @param int $direction
 *
 * @return callable<back>|callable<forward>
 */
public function getMover($direction)
{
}

can't be declared inline.

@mvriel
Copy link
Member

mvriel commented Jul 19, 2013

I am definitely not opposed to an inline syntax as an alternative to a declaration, for example:

@return callable {
    This is my summary.

    And this is the description.

    @return boolean
}

would be the same as

@declare callable myCallable {
    This is my summary.

    And this is the description.

    @return boolean
}

@return callable<myCallable>

As @boenrobot correctly spots this is a re-use of the Inline PHPDoc idea that has been floating for some time (at least in my mind), and is the declaration necessary for element re-use.

And re-use should be limited by scope, thus a declare in a method is only applicable in that method but a declare at the class is applicable in the class and every property, method and constant.

With regards to the description, this should go inline with the tags, as a complete PHPDoc definition.

@boenrobot
Copy link
Contributor

You know... come to think of it... maybe we're going this the wrong way... at least as far as closures are concerned...

How about we make the value between "<" and ">" be the name of any PHP function or method (when the primary type is "callable" that is)? If you use anonymous functions (which is obviously the primary use case), you can make a special PHP file that only phpDocumentor reads, and which contains non-anonymous such declarations, with docs and everything. As far as IDEs and QA tools are concerned, those functions do exist in your special file, but as far as the runtime is concerned, it's actually calling an anonymous function that happens to have the exact same prototype and purpose. phpDocumentor and IDEs already support finding functions within conditions, so making this special file "safe for inclusion" is a piece of cake:

<?php
//Suppose "closures.php" in the project root (i.e. not meant for inclusion in actual use)...
if (false) {//... but just in case

    /**
     * Closure's short description
     *
     * Closure's long description.
     *
     * @param int $param1
     * @param string $param2
     * @return bool TRUE on success, FALSE on failure.
     */
    function back($param1, $param2)
    {
    }
}
<?php
//Actual file to be included in real use
/**
 * 
 * @param int $direction
 *
 * @return callable<back>
 */
public function getMover($direction)
{
}

This should actually be FAR more trivial to implement than the above plans (since the only thing missing is the recognition of the chevrons' value and the creations of links for it).

Thoughts?

@eborden
Copy link
Author

eborden commented Jul 19, 2013

Closure definitions in a separate file is untenable. PHPDocumentor is effective because all descriptions stay localized to the code they are describing. Additionally closures are "one offy" by nature and having to document them elsewhere is just not in the spirit of the code.

@boenrobot
Copy link
Contributor

Additionally closures are "one offy" by nature

True, but the "callable" type isn't necessarily such - you're allowed to also return a string that is the name of an existing non-anonymous function or an array with an object/method, which can then be called (hence "callable").

@mvriel
Copy link
Member

mvriel commented Jul 19, 2013

I do not feel much for this suggestion because I think that

  1. large applications could very well use a large amount of closures and as such this file would be big
  2. when reading the source code you'd have to alternate between two different files to get a feel for the syntax
  3. using 'real' code to describe elements with phpDoc introduces another language and more complexity
  4. we are describing templates, or definitions, of closures; real code describes implementations
  5. this file would probably confuse users that are not knowledgeable about this mechanism (I know I would go wtf when encountering a file containing empty function declarations)

@boenrobot
Copy link
Contributor

  1. No need for such a file to be a single one - larger projects could make several files, with each grouping closures by purpose or whatever (maybe even define a dummy classes with dummy functions 😆 ). As far as phpDocumentor is concerned, it's just a normal source file.
  2. The same applies to any OOP code - I mean, think how many times you have one class' method accept as an argument/return another class/interface - If you want to get a sense of the code, you'd have to alternate between those too, so I don't see how this is different.
  3. "Another language"?!? It's still PHP. It's @declare which introduces a new syntax (and thus a new "language" of sorts). "Complexity"? As much as a separate CSS file introduces a complexity over a style attribute - the complexity is repaid in reusability (i.e. you can use a single closure definition across multiple files).
  4. Fair enough... although I guess an empty function isn't exactly "real" code, especially if surrounded by a catch-all like "if(false)".
  5. Projects should be encouraged to keep those files separate from the main project's source, as a way to avoid such confusions. BTW, I know for a fact NetBeans uses such files in order to generate docs for internal/PECL PHP functions. Because they are in a dedicated folder, no one seems to get WTF moments... well, except maybe the first time for 2 seconds, after which it clicks.

@mvriel
Copy link
Member

mvriel commented Jul 19, 2013

I feel that userland PHP code should not be required to satisfy documentation purposes; in addition I believe it will be too much hassle for the 90% use-case (which, I believe, is one-off calls and not re-usable calls).

@mvriel
Copy link
Member

mvriel commented Jul 19, 2013

Also, the @declare does not add much complexity if Inline PHPDoc and Generics find their way into the syntax as it would only be an additional tag with elements known in other parts of PHPDoc

@boenrobot
Copy link
Contributor

The "call" and/or "bind", sure - the 90% use case in indeed a one time call/bind.

The question is about the "declaration" though - is a "one time prototype" the 90% use case? I'm not so sure.

Many projects have the same prototype of callback in multiple places - the runtime may "bind" to this callback just once and/or call it just once, but when the same prototype is being available in multiple places, having to re-declare it multiple times becomes cumbersome.

Case in point - JavaScript events (and PHP systems that may try to emulate a similar kind of coding style) - most applications bind to events just once, but all events accept a single "event" object, and return a bool value that tells whether the event chain continues for this triggering. Now, sure, many events have multiple specific properties, so in a similar PHP application, you'd want to have multiple prototypes, but you can also find many events that do share the same prototype (e.g. in JS' case - onkey* events with one, onmouse* with another, etc.).

@mvriel
Copy link
Member

mvriel commented Jul 19, 2013

Why would a method similar to @declare not be reusable? If that is a concern we can specify that class-based declarations are public by nature and may be included from any location.

i.e.

@return callable<\My\Class::myCallable>

or

@return callable<\My\Class::@myCallable>

if you'd want to be more explicit that it is a declaration.

@boenrobot
Copy link
Contributor

OK, that one might work... with both syntaxes being allowed - the first when a prototype of a real function/method is addressed, and the latter when a declaration in a class is addressed.

But if we're going to go on such kind of a route, I guess we should also go the extra mile and say file-level declarations are global across the entire project run - and let them be namespaced (and be relative to the root by default, unless a slash is present - like how tags are). e.g.

/**
 * Assume a file level docblock...
 *
 * @declare callable \My\Project\myCallable {{
 * Addressed the first and second time.
 * ...
 * }}
 *
 * @declare callable myCallable {{
 * Addressed the third, forth and fifth time
 * ...
 * }}
 */

//Later...

/**
 * @return callable<\My\Project\@myCallable> Or maybe...
 */
/**
 * @return callable<@\My\Project\myCallable> ... to avoid inconsistency between declaration and addressing.
 */
//...
/**
 * @return callable<@myCallable> Same as...
 */
/**
 * @return callable<\@myCallable> ... or rather ...
 */
/**
 * @return callable<@\myCallable>
 */

One may argue the "@" is syntactically confusing (due to it also being used for tags), but I don't see a better character as an alternative.

@eborden
Copy link
Author

eborden commented Jul 20, 2013

I'm curious why callables in general are being drawn in to this discussion. PHP has 3 types of callables:

  1. A closure
  2. A class that implements callable
  3. A named function decleration

Item 1 does not currently have a format for documentation. 2 and 3 can already be documented with normal doc blocks. Am I missing something?

@eborden eborden closed this as completed Jul 20, 2013
@eborden eborden reopened this Jul 20, 2013
@eborden
Copy link
Author

eborden commented Jul 20, 2013

Woops sorry about that, accidentally hit the wrong button.

@pavelkouril
Copy link

@mvriel Func and Action do cover it, I just forgot to put it into the code block (I'm not all that familiar with GitHub, I just it once in a while) and not have the angle brackets removed. To reiterate on my example (with complete code samples, although kinda made up ones).

/**
 * @param Action<string> $fn Function to do with every element of array
 * @param array $in
 */
function doForeach($fn, $in)
{
   foreach ($in as $item) $fn($item);
}

/**
 * @param Func<int, int> $fn Function to apply to every element of array
 * @param array $in
 */
function map($fn, $in)
{
    $out = array();
    foreach ($in as $item) $out[] = $fn($item);
    return $out;
}

Of course it would be better to use some "more" phpesque names, but this kinda of apporach seems best for me (but I may be kind of biased because I switch between C# and PHP back and forth) -- especially the recognization between callback that returns a value and one that does not.

Additional information in documentation block about anonymous function (apart from typing) -- maybe description for parameters would make sense, but naming is pointless (because when you expect some anonymous function, you only care about types of parameters; not names). The description could help though, so I'm not againts that.

Side-note: The angle brackets look like "generics" though and I'm not sure about phpDoc's stance on Generics. I personally, when developing, would find them as a great addition, but I'm not sure about other people; is there an issue about generics which was rejected/is currently under discussion?

@boenrobot
Copy link
Contributor

PHP has 3 types of callables:

  1. A closure
  2. A class that implements callable
  3. A named function decleration

Item 1 does not currently have a format for documentation. 2 and 3 can already be documented with normal doc blocks. Am I missing something?

Yeah, about 3... while you can declare "callable" as a type (which will let you accept/return a named function), there's no way to specify the expected prototype of a named function/method, just as there's no way to specify prototypes for anonymous functions either. Named functions are on equal footing with closures as far as documentation about them is concerned.

is there an issue about generics which was rejected/is currently under discussion?

No, since PHP doesn't really need generics, due to its loose type-ness. There are other C# features that have been brought up, and ultimately taken down (in some cases, unfortunately so, IMHO), such as get/set syntax for properties for example.

@pavelkouril
Copy link

@boenrobot: I meant Generics in phpDoc, not in language itself.

@boenrobot
Copy link
Contributor

@pajousek No, there aren't generics in phpDoc, which is why we're considering this particular syntax. Considering PHP's nature, I'm not sure how "generics" would look in phpDoc terms.

@dgmike
Copy link

dgmike commented Nov 15, 2014

👀

@iluuu1994
Copy link

Is this still being considered?
The proposed syntax looks a little bloated. Swift has pretty syntax for closures:

(Type1, Type2) -> ReturnType

For PHP it would probably make more sense to use the : symbol for the return type to be consistent with function return types:

/**
 * @param (Type1, Type2): ReturnType $callback
 */

I could see how this would be difficult to implement for ambiguity reasons though.

@censys-git
Copy link

Or how about:
@param callable(string, int):array $mycallback my description
?
callable simply identifies that it is a closure or similar, the parenthesis contain standard parameter types (including allowing ...args), and the colon:type defines the return type (can be or'd as well). The rest of the line is standard parameter definition.

JSDoc (and Google Closure) use something similar except in curly braces

@param {function(string, number):boolean} mycallback my description

I'd just like to get my IDE to stop giving me warnings for unrecognized properties and methods because it doesn't know what the parameters of the callable function are.

@renekoch
Copy link

renekoch commented Mar 5, 2017

Is this still in consideration? This has long been a wish of mine. Any syntax will do, tho @iluuu1994 / @sl1331 suggestion seems sane in a PHP context.

@jaapio
Copy link
Member

jaapio commented Mar 7, 2017

Yes it is, but most likelly it will be implemented in phpdocumentor v3. If someone is able to create a pr for this that might speed up the progres.

@the-liquid-metal
Copy link

Sorry if out of topic.
Rather than there is no solution, i have created my own sollution. This is simple, common sense, and without introduce new syntax:
https://stackoverflow.com/questions/16589902/syntax-of-closure-in-phpdoc/51208203#51208203

Of course permanent solution (with new syntax) is absolutely better.

@mvriel mvriel added this to Near Future in Wishlist Nov 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Wishlist
Near Future
Development

No branches or pull requests

10 participants