Crell,
@Crell@phpc.social avatar

Looking for feedback.

I have a lib build for PHP 7 that has 3 methods:

addFoo($id, $priority)
addFooBefore($id, $before)
addFooAfter($id, $after)

($before/$after refer to some other $id.)

For PHP 8, I am strongly considering just assuming named args and changing it to:

addFoo($id, $priority, $before, $after);

Specify only one of priority/before/after, by name. Multiple is an error.

Is this a good idea/bad idea, and why?

Crell,
@Crell@phpc.social avatar

Thanks everyone for the feedback. Basically everyone hated the idea, so I didn't do that. 🙂

Instead, I went with a fake-ADT approach, which allowed me to simplify a ton of code, remove a lot of duplicate code, and have only the slightest API break in an edge case I don't think anyone has actually used.

v2 is looking kinda pretty.

https://github.com/Crell/Tukio/pull/24

gmazzap,
@gmazzap@phpc.social avatar

@Crell Do you think we'll ever have real ADT in PHP?

Crell,
@Crell@phpc.social avatar

@gmazzap Ilija and I want to work on it and have a decent amount of design already done for it. No promise on timeline, but the rfc text for it and for pattern matching (which will be required) are already written. (The former needs some updates, of course.)

gmazzap,
@gmazzap@phpc.social avatar

@Crell that would great. Thanks!

ramsey,
@ramsey@phpc.social avatar

@Crell I’m torn on this kind of thing, too, and whenever I encounter this, I wish PHP had overloading, but I think that would cause a whole slew of new problems.

bobmagicii, (edited )
@bobmagicii@phpc.social avatar

@Crell we have one method on tag cloud widget like that.

->ItemNew(string $Name, string $Icon, [...], ?int $Before=NULL, ?int $After=NULL);

but its just a wrap around ItemAfter and ItemBefore for the days i feel like it. and i think my hasty condition check prefers after over before.

my editor is doing a fantastic job of handling the suggestions for named things. the AIO api fails a "is this a unit" smell test but i don't mind the AIO wrapper method existing for end-user end-app stuff.

karptonite,

@Crell for me, it isn’t enough that the proposed new version is better (if it is). To change a public API like that, even in a major version change, I would want the new version to be unambiguously a substantial improvement over the old version. I don’t know that this rises to that level.

Crell,
@Crell@phpc.social avatar

@karptonite Ideally I can do this in a way that has a BC shim for a while.

karptonite,

@Crell I guess, but that only delays the inevitable updates for the user; the bar to force such a change, even eventually, should be at least “is unambiguously substantially better.”

awoodsnet,
@awoodsnet@phpc.social avatar

@Crell when a method can take multiple parameters, and they’re all optuonal - that feels like a red flag to me. i like your current implementation better. a method name like addFooBefore shows intent over a universal addFoo

robert,
@robert@flownative.social avatar

@Crell Why not take the opportunity and make the method names more descriptive? How about insertFoo() and appendFoo()? Or something more meaningful from the respective domain?

Crell,
@Crell@phpc.social avatar

@robert They are descriptive in their domain at the moment. (It's event dispatcher registration. Add a listener add a listener with a sort priority, or add a listener before/after some other listener. Where most of the time I expect you don't care about order so don't use anything.)

shudder,
@shudder@phpc.social avatar

@Crell If I had to choose I'd say this change is a bad idea. Having single method doesn't bring any value but confusion (call itself is readable tho).

That said, these methods require knowledge of internal structure, so they're either redundant (order of calls in local scope) or serve as a convenience that deals with temporal coupling.

I'd simply remove them, but I feel that "bad code should be hard to write" is not a popular philosophy in php world, so it's a matter of picking fights I guess.

Crell,
@Crell@phpc.social avatar

@shudder In their domain, its logic that makes sense to expose. cf: https://phpc.social/@Crell/111076128543782413

I generally agree with "bad code should be hard to write." What that translates into in practice is not always crystal clear, however. 🙂

kniziol,
@kniziol@phpc.social avatar

@Crell What about this idea? Is that possible?

dantleech,
@dantleech@fosstodon.org avatar

@Crell having 4 params means avoidable runtime errors...

$this->addFoo($id, Ordering::prioritised(10))  

Doesn't have that, this is weirder:

$this->addFoo($id)->withPriority(10);

$this->addFoo($id)->before(12)  

but depends, I'd say 3 methods is less bad than one with an avoidable error in it.

Crell,
@Crell@phpc.social avatar

@dantleech The first is a challenge without ADTs. Though... I suppose might be possible to hack together.

I've never been a fan of the kind of chaining in the 2nd example, and making that work would be a massive change to multiple libraries.

dantleech,
@dantleech@fosstodon.org avatar

@Crell yeah, I'd challenge the 2nd approach :D

Not sure why the 1st needs ADTs? It can be done with static constructors already and presents an unambiguous API.

Crell,
@Crell@phpc.social avatar

@dantleech It could be done without ADTs, but ADTs would make it 10x easier and more logical, since the exclusivity is built into the language.

Crell,
@Crell@phpc.social avatar

@dantleech To make things more exciting, of course, there is a second optional argument other than $id ($type) that is also consistent across all 3 options. So... I suspect named args is going to always be a thing in practice. That's why I'm so tempted by just going all in on named args.

dantleech,
@dantleech@fosstodon.org avatar

@Crell slightly easier (assuming we're talking about things like Rust Enums), but the safety (assuming no bugs from vendor) is the same to the user?

but also: what is going to cost more - user having to instantiate the Value Object once (but it will work forever) or user having to guess how to use it (e.g. somewhere before ID 12 but after 6! and then based on the given priority! oh wait...) and potentially debug runtime errors sooner or later.

Anyway, named args. are prob. fine 😅

Crell,
@Crell@phpc.social avatar

@dantleech You just argued they're not fine! 😛

(And yes, ADTs ~ Rust Enums. PHP doesn't have that, but it's on the long-term todo list.)

dantleech,
@dantleech@fosstodon.org avatar

@Crell well, there's being "pure" and there's being practical. I'd always argue for the first, but it depends 😅

heiglandreas,
@heiglandreas@phpc.social avatar

@Crell I'd expect an enum as 3rd parameter.

Location::Before or Location::After....

Crell,
@Crell@phpc.social avatar

@heiglandreas What would that add, since you need to specify a value for each of those options? We don't have ADTs yet... 🙂

heiglandreas,
@heiglandreas@phpc.social avatar

@Crell 🙈 My mistake... TL;DR 😂

No. Forgot the 4th parameter. Which would be the ID.

But that then requires too many args....

When they are all optional params, then your proposed "before: " or "after: "works fine but you will have to check whether both are provided to fail. Or define a default order. When both are provided, AFTER will win.

Using a special object that takes an enum and an id would probably be too much overkill though the cleanest solution...

Crell,
@Crell@phpc.social avatar

@heiglandreas Yeah, that's over-engineered. I already have a one liner that confirms only one is non-null, for the attribute version. There are other optional args on all of them I left out of the example. All 3 have one required arg, a callable.

Crell,
@Crell@phpc.social avatar

Additional context:

  • I predict most uses won't use any of the three args. They're all optional.
  • Most often, people will use an attribute instead that I have already combined into one, since those should always be using named args anyway. That makes the code a bit easier if they can be combined.
s3rvant,
@s3rvant@fosstodon.org avatar

@Crell

Usually best to let each function do a single task as is easier to maintain / test / etc. so keeping them separate is preferred.

However could make a new function similar to what you're suggesting that validates the input and then triggers the appropriate function based on those inputs.

End result would be using a single function throughout code that allows triggering any of the 3 functions.

Crell,
@Crell@phpc.social avatar

@s3rvant In practice, does that even make sense? In this case, I suspect there would be less code folding them together, not more.

s3rvant,
@s3rvant@fosstodon.org avatar

@Crell

I suppose that would depend on how much code is repeated between the 3.

In practice it often doesn't make much difference.

derickr,
@derickr@phpc.social avatar

@Crell Bad idea. You need then check in the method about whether arguments clash. Your method name is also no longer self describing.

Crell,
@Crell@phpc.social avatar

@derickr The check is a one-liner, which I already have for the attribute version.

And it's still self describing, I'd argue. addThing($thing, before: $other), addThing($thing, after: $other), etc.

derickr,
@derickr@phpc.social avatar

@Crell Using arguments to change behaviour is IMO an anti pattern

ghostwriter,
@ghostwriter@phpc.social avatar

@derickr not if the arguments are used to define the behavior of the method rather than changing it dynamically.

gmazzap,
@gmazzap@phpc.social avatar

@Crell @derickr It reminds me of WordPress' $args pattern. In facts, it isn't much different then a "WordPressy"

addThing($id, ['after' => $a])

Yours is better because type safe, but it suffers of the same "weak signature" problem.

I would use a signature like:

addThing(string $id, Position $p)

where Position has 3 named constructors and a private constructor. Used like:

addThing($i, Position::before($b))

addThing($i, Position::after($a))

addThing($i, Position::priority($p))

Crell,
@Crell@phpc.social avatar

@gmazzap @derickr The "options array" pattern is an abomination, and always has been. :-) Named args are way better, though as you note, not perfect.

Crell,
@Crell@phpc.social avatar

@gmazzap @derickr What you're suggesting is essentially the "knock-off ADTs" discussed in another branch of the thread. I'm torn on if the API that results is convenient enough.

gmazzap,
@gmazzap@phpc.social avatar

@Crell @derickr Indeed. If PHP enums would allow for constructor args you could use a enum for it ;) That would be the idiomatic approach in many other languages.

  • All
  • Subscribed
  • Moderated
  • Favorites
  • php
  • kavyap
  • thenastyranch
  • mdbf
  • DreamBathrooms
  • ngwrru68w68
  • magazineikmin
  • InstantRegret
  • Youngstown
  • vwfavf
  • slotface
  • everett
  • osvaldo12
  • rosin
  • khanakhh
  • megavids
  • tester
  • Durango
  • tacticalgear
  • GTA5RPClips
  • cisconetworking
  • ethstaker
  • cubers
  • normalnudes
  • modclub
  • provamag3
  • Leos
  • anitta
  • JUstTest
  • All magazines