-
Notifications
You must be signed in to change notification settings - Fork 69
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
First implementation of fluent interface #78
base: master
Are you sure you want to change the base?
Conversation
Some ideas that could be implemented:
|
I'm 👎 here, having a fluent interface would be annoying when you need to mix function from this package with your own functions (or functions from other packages). I think instead we should provide curried versions of the functions and a https://medium.com/making-internets/why-using-chain-is-a-mistake-9bc1f80d51ba explains in details (in javascript but some problems apply to PHP to) why such an interface comes with lots of problems. |
@jvasseur can you be more specific?
Can you give an example, things like
Could you give a syntax example of how you envision this? I'm definitely open to suggestions.
Could you specify which problems you think apply to PHP? For me this is just a small wrapper that allows me to create more readable code without taking away any of the flexibility, I'm not proposing this implementation replaces the functional approach... |
@SamMousa I tend to agree with @jvasseur , I think the only way you could do fluent collection like stuff would be if you provided macros like laravel's collection package to allow patching in, but that comes at the cost of discoverability. The issue he's referring to is, let's say you built your own function like The other minor issue is that any new functions/changes need to be kept in sync and tested Regrading iter vs functional syntax, here are some examples: https://github.com/krakphp/fn - this is an example of what you can do with curried functions and compose/pipe chains. and here's a syntax comparison (as shown in the article as well) <?php
// imperative
$values = [1,2,3];
$values = map(function() {}, $values);
$values = filter(function() {}, $values);
// imperative chained
$values = filter(
function() {},
map(
function() {},
[1,2,3]
)
);
// functional curried w/ compose
$values = compose(
filter(function() {}),
map(function() {})
)([1,2,3]);
// functional curried w/ pipe
$values = pipe(
map(function() {}),
filter(function() {})
)([1,2,3]);
// fluent chaining
$values = iter([1,2,3])
->map(function() {})
->filter(function() {}); |
What I mean is if you have your function $fluent
->filter(...)
->myFunction()
->map(...) |
@ragboyjr thanks for the examples! I'm not sure I fully agree with the reasoning though.
Because the fluent version is iterable itself you can just pass it into your function. $fluent = new Fluent(['a','b']);
$result = chunkBy($fluent->map(...)->slice(1, 6)) Admittedly, you lose the syntactic sugar a bit. $fluent = new Fluent(['a','b']);
$fluent->map(...)
->slice(1, 6)
->via($chunkBy)
->map(...); The issue with PHP is that you cannot reference a function naturally, unless it is a closure stored in a variable.
This is true, but this is also the case for curried functions, as you can see in the library you mentioned, it comes with both curried and uncurried functions as well as constants so you can reference those functions. One thing PHP has that the other languages don't is that it allows objects to be |
To extend the example assuming you don't have a clean reference to your function: $fluent->map(...)
->via(function(Fluent $fluent): iterable {
return chunkBy($fluent);
})
->slice(1, 6); This ticks all my boxes:
I understand the arguments given against this, but PHP is not a functional programming language. It has drawbacks but also advantages. The advantage here is |
In the lib I mentioned, the curried functions are automatically generated. So it's an automated process that uses php parser to generate the code. Which is something you could do with a fluent interface as well I supposed. Regarding that via example, that's not so bad, if you're functions were curried, then it should work fine: <?php
namespace Acme\Util;
const chunkBy = chunkBy::class;
function chunk($args) {
return function($value) use ($args) {};
} use Acme\Util;
$fluent->map()->via(Util\chunk(args)); |
We could even omit the functions and just use |
@SamMousa ya, I think that's what moneyphp does - https://github.com/moneyphp/money/blob/master/resources/generate-money-factory.php |
Yeah, they do it for static constructors. I think I've given counters to most arguments, are there more issues with this solution? |
src/FluentIterator.php
Outdated
*/ | ||
public function via(callable $callable): self | ||
{ | ||
return new static(call_user_func($callable, $this)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you can provide an interface that allows passing extra args:
public function via(callable $callable, ...$args): self
{
return new static($callable($this, ...$args));
}
this would remove the need to create closures as long as you need to pass the iterator as the first argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of that as well, we could even make it dynamic like this:
public function via(callable $callable, array $before = [], array $after =[]): self
{
return new static(call_user_func_array($callable, array_merge($before, [$this], $after)));
}
Although that might be a bit too much.
Note that in PHP you generally don't start with a callable to begin with, so a closure is often required regardless of the support for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that in PHP you generally don't start with a callable to begin with, so a closure is often required regardless of the support for this.
Why? All functions can be used as callable by passing the fully qualified name as a string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jvasseur true, but that's not good in my opinion.. It removes all static checks or ide support.
I'd rather wrap my built-in function in a closure than use a quoted string to reference a function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now I propose leaving it as it is now.
@nikic do you have an opinion? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks pretty nice, I think we should add it.
Co-Authored-By: Nikita Popov <[email protected]>
Co-Authored-By: Nikita Popov <[email protected]>
First draft of #45