在PHP中创建可选生成器

huangapple go评论52阅读模式
英文:

Creating optional generators in PHP

问题

I'm struggling with creating optional generator functions in PHP. Essentially, I'm trying to build a function that only turns into a generator if there are multiple values and behaves like a normal function if only a single value is present.

Per the official documentation:

Any function containing yield is a generator function.

A simple yet easy rule to follow by. However, with things I've recently been working on I've been having trouble with this. In particular, I haven't found a clean way to implement generator functions in a way that allows me to only return a generator if necessary. A quick example:

function generatorTest() {
	if(false) {
		yield 0;
	} else {
		return 0;
	}
}

var_dump(generatorTest()); // object(Generator)

Looking at the code, one might expect to only get the value, but as it turns out, a functions is turned into a generator really anytime it contains the yield keyword. Which is in line with the documentation, but questionable. Even if the yield keyword is unreachable, the function is turned into a generator. A simple way around this would be to return a closure that itself is a generator, so the function itself isn't turned into a generator:

function generatorTest() {
	if(false) {
		return (function() {
			yield 0;
		})();
	} else {
		return 0;
	}
}

var_dump(generatorTest()); // int(0)

And this, while not the cleanest workaround, actually works. However, with the application I'm currently working on, this is rather impractical, because I'm not just yielding/returning a simple value but instead iterate complex requests to an API. As a result, I'd have two blocks that are rather similar and unnecessarily bloat the function.

I also looked into alternatives and came up with this:

function generatorTest() {
	$ret = (function() {
		if(false) {
			yield 0;
		} else {
			return 0;
		}
	})();

	try {
		return $ret->getReturn();
	} catch(Exception $e) {
		return $ret;
	}
}

var_dump(generatorTest()); // int(0)

Instead of returning a function that is a generator if necessary, it always creates a generator and checks if any return value is present. It doesn't bloat the code because it always returns a function with only the return and yield actually branching off, everything else would have to be written only once. However, it basically intentionally uses exceptions to check if it's actually a generator or just a normal value that was returned. Which really is kinda ugly.

So here is the question, is there a better way to create optional generator functions, as in functions that are only generators if a condition applies and otherwise will just return as normal? Am I entirely off on my modeling and should look at a different approach? Or is my case so out-of-this-world that my ugly solution is pretty much the best for that situation?

Edit: Additional details on the application

The application in question is this MediaWiki bot. Specifically, I'm currently working on this function, which already uses the second method I mentioned in my example, but stores all data it loads before returning the generator, which pretty much defeats the purpose of the generator and is neither efficient nor useful because I have to wait like an hour before I know if it actually works when performing a large number of requests.

The function mentioned above is used to query revisions on a wiki, and the idea was to provide different return types depending on what you want to query. Since overloading is not really possible in PHP, this is the result I got. When calling the function with a single id, it would look like this:

require_once("bottibott_v5/src/autoload.php");

$bot = new Bot(URL); // URL is the url to the api endpoint of the wiki
$revisions = new Revisions($bot);
$revisions->setIds("1");

var_dump($revisions->getRevisionsFromRevids()); // would output some kind of revision record, depending on the wiki

Meanwhile, when calling the same function with multiple ids, the request would look like this:

require_once("bottibott_v5/src/autoload.php");

$bot = new Bot(URL); // URL is the url to the api endpoint of the wiki
$revisions = new Revisions($bot);
$revisions->setIds(range(0, 10));

foreach($revisions->getRevisionsFromRevids() as $revision) {
	var_dump($revision); // would output each revision record individually
}
英文:

I'm struggling with creating optional generator functions in PHP. Essentially, I'm trying to build a function that only turns into a generator if there are multiple values and behaves like a normal function if only a single value is present.

Per the official documentation:
> Any function containing yield is a generator function.

A simple yet easy rule to follow by. However, with things I've recently been working on I've been having trouble with this. In particular, I haven't found a clean way to implement generator functions in a way that allows me to only return a generator if necessary. A quick example:

function generatorTest() {
	if(false) {
		yield 0;
	} else {
		return 0;
	}
}

var_dump(generatorTest()); // object(Generator)

Looking at the code, one might expect to only get the value, but as it turns out, a functions is turned into a generator really anytime it contains the yield keyword. Which is in line with the documentation, but questionable. Even if the yield keyword is unreachable, the function is turned into a generator. A simple way around this would be to return a closure that itself is a generator, so the function itself isn't turned into a generator:

function generatorTest() {
	if(false) {
		return (function() {
			yield 0;
		})();
	} else {
		return 0;
	}
}

var_dump(generatorTest()); // int(0)

And this, while not the cleanest workaround, actually works. However, with the application I'm currently working on, this is rather impractical, because I'm not just yielding/returning a simple value but instead iterate complex requests to an API. As a result I'd have two blocks that are rather similar and unnecessarily bloat the function.

I also looked into alternatives and came up with this:

function generatorTest() {
	$ret = (function() {
		if(false) {
			yield 0;
		} else {
			return 0;
		}
	})();
	
	try {
		return $ret->getReturn();
	} catch(Exception $e) {
		return $ret;
	}
}

var_dump(generatorTest()); // int(0)

Instead of returning a function that is a generator if necessary, it always creates a generator and checks if any return value is present. It doesn't bloat the code because it always returns a function with only the return and yield actually branching off, everything else would have to be written only once. However it basically intentionally uses exceptions to check if it's actually a generator or just a normal value that was returned. Which really is kinda ugly.

So here is the question, is there a better way to create optional generator functions, as in functions that are only generators if a condition applies and otherwise will just return as normal? Am I entirely off on my modeling and should look at a different approach? Or is my case so out-of-this-world that my ugly solution is pretty much the best for that situation?

Edit: Additional details on the apllication

The application in question is this MediaWiki bot. Specifically, I'm currently working on this function, which already uses the second method I mentioned in my example, but stores all data it loads before return the generator, which pretty much defeats the purpose of the generator and is neither efficient nor useful, because I have to wait like an hour before I know if it actually works when performing a large number of requests.

The function mentioned above is used to query revisions on a wiki and the idea was to provide different return types depending on what you want to query. Since overloading is not really possible in PHP, this is the result I got. When calling the function with a single id, it would look like this:

require_once("bottibott_v5/src/autoload.php");

$bot = new Bot(URL); // URL is the url to the api endpoint of the wiki
$revisions = new Revisions($bot);
$revisions->setIds("1");

var_dump($revisions->getRevisionsFromRevids()); // would output some kind of revision record, depending on the wiki

Meanwhile when calling the same function with multiple ids, the request would look like this:

require_once("bottibott_v5/src/autoload.php");

$bot = new Bot(URL); // URL is the url to the api endpoint of the wiki
$revisions = new Revisions($bot);
$revisions->setIds(range(0, 10));

foreach($revisions->getRevisionsFromRevids() as $revision) {
	var_dump($revision); // would output each revision record individually
}

答案1

得分: 1

如果您只有1个值,您可以将其转换为一个数组,以便您始终从函数中获得任何值。主要优点是您只需在生成器上进行foreach循环并执行相同的逻辑...

function generatorTest(mixed $range)
{
    if (!is_array($range)) {
        $range = [$range];
    }
    foreach ($range as $individual) {
        yield $individual;
    }
}

var_dump(generatorTest(1));
// class Generator#1 (0) {
// }

var_dump(generatorTest(range(0, 10))); 
// class Generator#1 (0) {
// }
英文:

If you have 1 value, you could just convert this into an array, so that you always yield any value from the function. The main advantage is that you just foreach over the generator and do the same logic...

function generatorTest(mixed $range)
{
    if (!is_array($range)) {
        $range = [$range];
    }
    foreach ($range as $individual) {
        yield $individual;
    }
}

var_dump(generatorTest(1));
// class Generator#1 (0) {
// }

var_dump(generatorTest(range(0, 10))); 
// class Generator#1 (0) {
// }

答案2

得分: 1

Here's the translation of the provided text:

"实际上,我同意在所有情况下都使用生成器来使代码更加简洁和易于使用!

function generatorTest(mixed $arr): Generator
{
    yield from is_array($arr)? $arr: [$arr];
}

但是有一种方法可以使您的代码更加清晰,您可以使用两个函数:

function makeGenerator(array $array):Generator
{
    yield from $array;
}

function generatorTest(bool $generator = false):Generator|int
{
    $array = [1, 2, 3];
    return $generator? makeGenerator($array): 5;
}

在您的情况和代码中,可以像这样使用(从第113到123行):

return $generator && count($revisions) == 1? $this->makeGenerator($revisions): reset($revisions);

还有一个新的函数来生成生成器(在第125行):

protected function makeGenerator(array $revisions){
    yield from $revisions;
}
英文:

Actually I agree with making generator in all conditions to make code more concise and simple to use!

function generatorTest(mixed $arr): Generator
{
    yield from is_array($arr)? $arr: [$arr];
}

But there is a way to do as you wish a little more cleaner

Use two functions

function makeGenerator(array $array):Generator
{
    yield from $array;
}

function generatorTest(bool $generator = false):Generator|int
{
    $array = [1, 2, 3];
    return $generator? makeGenerator($array): 5;
}

Which In your case and code would be like this from line 113 to 123:

return $generator && count($revisions) == 1? $this->makeGenerator($revisions): reset($revisions);

And a new function to make generator at line 125:

protected function makeGenerator(array $revisions){
    yield from $revisions;
}

huangapple
  • 本文由 发表于 2023年6月13日 02:04:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76459230.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定