Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:118395 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 58252 invoked from network); 10 Aug 2022 12:15:29 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 10 Aug 2022 12:15:29 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id EA35F1804D0 for ; Wed, 10 Aug 2022 07:17:15 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-1.6 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_ENVFROM_END_DIGIT, FREEMAIL_FROM,FREEMAIL_REPLYTO_END_DIGIT,HTML_MESSAGE, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE, SPF_PASS,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=3.4.2 X-Spam-ASN: AS15169 209.85.128.0/17 X-Spam-Virus: No X-Envelope-From: Received: from mail-pg1-f173.google.com (mail-pg1-f173.google.com [209.85.215.173]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Wed, 10 Aug 2022 07:17:15 -0700 (PDT) Received: by mail-pg1-f173.google.com with SMTP id c24so9620158pgg.11 for ; Wed, 10 Aug 2022 07:17:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=to:subject:message-id:date:from:reply-to:mime-version:from:to:cc; bh=0NfvtAljxayzhIWOgtqFG8uQYaMuSaKT4Xkvaui/DgM=; b=fobtd2ypPJsBPsTRRckUmk8lCNSTETzQHEra9sXmHQHS0vQuJK9zqH6jvdxiGLuK6P F/yQkHXNZHpdya3JigaKmXjfFL8kjyVH0udv/h5jQL1mqhdNpIIPD436ii6KwgGllgJo XFcMB1cGJWYdHXm/Py65RLULrHw27dYcacoDlZOBRTBobUEx9yXAyhkw2jQIEEsUV9F4 sGDyBIDTD8uAaQYdPc1TZrnEusxG6vJzw0eFxVhrW/U8qQj0Bl8BZrPW2+eiIZf8jFUc egKTeO/8iw3C2JF4TvDBj/eem97/+EUvHXV983TYHh4+tNbsJLhAS52OXZQ0/hn0JopH 4Xwg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=to:subject:message-id:date:from:reply-to:mime-version :x-gm-message-state:from:to:cc; bh=0NfvtAljxayzhIWOgtqFG8uQYaMuSaKT4Xkvaui/DgM=; b=SKQyGgfimcPEIKS4BulXFs8monad9/OgQ9KEDQi75BySDinlrUOLxZrIlPygzwFymi jirsfdsPBeOHF2fIPY8Ca3lIxgAwa0yyQH0U4O2AgNBuYMGMbMua7hlslWFdjGbKMH1V Mi6nGsQZvv9Pb2qk8niSTip4I9+72BqxH2zZ04l4tEav0E0sw5ef9zy7FRgIdhGjhGjv C6Ub3ATVGTpzmt017Nlkki71vsMwglThl2Jt4bxiR+uMxEWWiw+v5Za8EV3zGrqwPaNx OiGHERRaloa+1GRLV94wTGH3uvK9olauJUxupPj1+nZIq0ALD2lGtGbGczEsSMBub2vX +tEQ== X-Gm-Message-State: ACgBeo1ZLPK0yAnFOZA5pO5fSAM9OUwhRXM3r3mPlRU/r3sBCbc9WGm5 fGmoltId0KvZxUqesI0Ex6AePeQdFjx4LlyNhSHYGUwXuO8cnjQg X-Google-Smtp-Source: AA6agR6o2UsmLHOLvn3gJR6vQa9Nhd1K570pVPWfue8J8nysGbZCrgTtGlimF3XyR/J7RDNXLqzGAnyz3RwwXA6+Mjo= X-Received: by 2002:a63:1e4e:0:b0:41c:8e48:9807 with SMTP id p14-20020a631e4e000000b0041c8e489807mr22644909pgm.247.1660141034024; Wed, 10 Aug 2022 07:17:14 -0700 (PDT) MIME-Version: 1.0 Reply-To: autaut03@gmail.com Date: Wed, 10 Aug 2022 17:17:02 +0300 Message-ID: To: internals@lists.php.net Content-Type: multipart/alternative; boundary="0000000000009c43eb05e5e3b465" Subject: [Concept] Extension methods From: autaut03@gmail.com (Alex Wells) --0000000000009c43eb05e5e3b465 Content-Type: text/plain; charset="UTF-8" Hey internals. The idea is to introduce extension methods, similar to those in Kotlin, C#, Dart. For those unfamiliar, those are just regular functions with fancy syntax. However, I think having those will not only improve readability, but also cover some of the previously requested features. Say you have a `class Collection` and you want to add a new method `map(callable $callable): Collection`. The first instinct is to go into that file and add the method, this will work. But what if `class Collection` is defined in a vendor package? You could define a regular function like this: `map(Collection $collection, callable $callable): Collection`, then import it whenever you need to use that. This solution works, but in practice is rarely used. The reasons are: - there's no IDE completion: `$collection-> ` <- here I want IDE to auto-complete the `map` method somehow, but since it's a function this is impossible - it's ugly looking and hard to read: `getPromotions(mostExpensiveItem(getShoppingList(getCurrentUser(), 'wishlist'), ['exclude' => 'onSale']), $holiday);` - it's easy to mess up the order of arguments The pipe operator RFC [1] was trying to solve that but got rejected. Major libraries/frameworks like Laravel [2] and Carbon [3] use a solution with `__call` that allows users to define custom methods on their classes: `Carbon::mixin('toMyTimeFormat', fn () => $this->format('MM YYYY DD'));` Again, this solution: - doesn't offer IDE completion and doesn't offer IDE navigation - harder to understand - doesn't work with interfaces, traits, enums or primitives Other languages (Kotlin [4], C# [5], Dart [6]) I'm aware of solve this problem with extension methods. All of them use slightly different syntax, but the main idea is: - you define an extension method the same way you define a function, except you specify which type you're extending - you can use any type that a function can accept. This includes primitives, classes, interfaces, traits and enums - the type you're extending is implicitly bound to `$this` - you only have access to the public scope - you can't access private/protected members - you have to import those the same way you import functions. You can't define extensions globally In PHP this could look like this: ```php // Illuminate/Collection.php namespace Illuminate; class Collection {} // App/CollectionExtension.php namespace App; use Illuminate\Collection; extension CollectionExtension on Collection { function map(callable $callable): Collection { return new Collection(array_map($callable, $this->items)); } } // App/Business/Logic.php namespace App\Business; use extension App\CollectionExtension::map; (new Collection(1, 2, 3)) ->map(fn ($value) => $value + 1) ->map(fn ($value) => $value * 2); ``` The way this should work is PHP first checks whether `Collection` has `map` method. If one's missing, it gets all used extensions that match that method name, autoloads them (the same way other class-likes are loaded) and checks whether current type `Collection` matches the type specified in the extension. If so, calls the method, otherwise attempts to call __call. This same concept eliminates the need for scalar objects or scalar extension methods [7]. I'm guessing there will be problems with optimization and OPCache. What are your thoughts? References: - [1] - https://wiki.php.net/rfc/pipe-operator-v2 - [2] - https://github.com/laravel/framework/blob/9.x/src/Illuminate/Macroable/Traits/Macroable.php - [3] - https://github.com/briannesbitt/Carbon/blob/master/src/Carbon/Traits/Macro.php - [4] - https://kotlinlang.org/docs/extensions.html - [5] - https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods - [6] - https://dart.dev/guides/language/extension-methods - [7] - https://github.com/nikic/scalar_objects --0000000000009c43eb05e5e3b465--