0

Langs like Java knows about true method overloading:

class Overload {
  void demo (int a) {
   System.out.println ("a: " + a);
  }
  void demo (int a, int b) {
   System.out.println ("a and b: " + a + "," + b);
  }
}

Whether langs like PHP — doesn't. So when you stand in front of a problem of processing something depending of context, which ways will you think of and which usually choose?

Typical problem could be some abstract payment system, where outside user should not think about concrete payment provider details (which could be different) and just call Provider.pay(PayDetails). In interface of PaymentProvider we must fix that it works with PayDetails. But in concrete implementations concrete provider could work for example only with it's own ConcretePayDetails.

Is it worth to polyfill such things as ad-hoc polymorphism in langs that dont' support it?

How to better solve context-related methods dispatching in such langs?

Example of possible implementation available at https://github.com/garex/php-ad-hoc-polymorphism-polyfill/

gaRex
  • 308
  • 1
  • 10
  • 1
    This question is probably too broad in its current form, since the "correct" answer to "is it worth it?" will be very different depending on what you hope to accomplish with your simulated method overloads. Could you describe a specific example of overloading you wish you could do in PHP? – Ixrec Jan 02 '16 at 00:12
  • @lxrec, see "Typical problem could be some abstract payment system" – gaRex Jan 02 '16 at 01:01
  • You should create factories which wrap the process of creating and injecting the correct children/implementations of the `PayDetails` class or interface respectively. If you create the factories and still have doubts, there is something wrong, because you do not trust your code. – Andy Jan 03 '16 at 13:14

2 Answers2

8

Ad-hoc polymorphism (method “overloading”) is a quite unremarkable feature of a language and does not make it more expressive. I.e. there is literally nothing that overloading lets you do that you can't easily do without (ignoring generic programming). All it does is providing a shortcut syntax.

When a language with ad-hoc polymorphism (such as Java) compiles a call to Overload#demo(…), it sees the different overloads void demo(int) and void demo(int, int) as entirely different functions. To resolve the call, the compiler matches the call signature to the available overloads, and selects the best match. This can be re-implemented in a dynamic language by inspecting the number and types of arguments. When ad-hoc polymorphism is resolved based on argument types during runtime, this is actually more expressive than ad-hoc polymorphism, and might be called multi-methods instead.

But we don't need any fancy metaprogramming to get the equivalent of ad-hoc polymorphism. Instead, we can simply give a different name to each overload, and require the programmer to select the proper overload when writing the code. This is exactly equivalent to the stuff the Java compiler would be doing, although clearly more error prone. You actually see this sometime in C APIs where there might be a function something(int) and something2(int, int) where we have different numbers of arguments, or int atoi(const char*) and double atof(const char*) where we have different types. In your example, your interface might be implemented as Provider.payWithPayDetails(PayDetails) instead of Provider.pay(PayDetails).

So if ad-hoc polymorphism is pretty much useless, why do languages have this feature? There are two main reason:

  • In Java, ad-hoc polymorphism allows us to simulate optional arguments, and to provide multiple constructors with different signatures. E.g. we could initialize a point from three numbers Point(double x, double y, double z), or as a copy constructor from another point Point(Point orig). In my experience, it would have been better for the language to provide actual optional arguments, and to suggest the use of suitably named static factory functions instead of overloading constructors. Note that PHP has real optional arguments, and no need to fake them via ad-hoc polymorphism.

  • Operator overloading would usually be implemented using ad-hoc polymorphism, since you can't give a different name to the same operator just to use it for different types. You wouldn't want to have +int and +double (though OCaml actually does that with + and +. operators, I think). In a wider sense, any function can be seen as an operator (or at least an operation) on some value. This comes in quite useful when writing generic code, e.g. with C++ templates (“argument-dependent lookup”) or Haskell's generics. Especially with Haskell, we might have dozens of definitions for a global function like fmap, but this can be resolved during compilation to a single instance. However, this use case does not apply to Java where interfaces are the only mechanism to define abstract types.

amon
  • 132,749
  • 27
  • 279
  • 375
  • I dont' think it's useless. Also how is "payWithConcreteDetails" could be added in interface? If I develop abstract extensible interface like `Provider.pay(PayDetails)` I just can't know and not need to know about some `ConcretePayDetails`, which another programmer can add later. – gaRex Jan 02 '16 at 01:10
  • 1
    `atoi` and `atof` are a bad example IMO. They have different names because they are "overloaded" only on return types. (and the copmpiler can't guess from the arguments which version to call) A more appropriate example would be `fputc` and `fputs`, whose names have to be differentiated for lack of function overloading in C. – Laurent LA RIZZA Jan 02 '16 at 06:46
0

I think you have conception issues here. You are saying you want to have a generic "pay" method. The problem is that this method will always be specific to given payment provider. For example PayPal provider cannot take CreditCardDetails as an argument to "pay". And vice versa.

I would suggest a "factory" approach. May not be the best one in this case but it solves the overloading "issue". This is only a raw draft that needs more work but it show one of possible approaches.

interface PaymentProvider
{
    public function pay();
}

class PayPalProvider implements PaymentProvider
{
    private $details;

    public function __construct(PayPalPayDetails $details)
    {
        $this->details = $details;
    }

    public function pay()
    {
        //Do something with $this->details
    }
}

class ProviderFactoriesCollection
{

}

interface ProviderFactory
{
    /**
     * @returns PaymentProvider
     */
    public function create(array $data);
}

class PayPalProviderFactory implements ProviderFactory
{
    public function create(array $data)
    {
        // Do the magic here
    }
}

class PaymentProviderFactory
{
    private $providerFactoriesCollection;

    public function __construct(ProviderFactoriesCollection $providerFactoriesCollection)
    {
        $this->providerFactoriesCollection= $providerFactoriesCollection;
    }

    /**
     * @returns PaymentProvider
     */
    public createFromInput($type, array $details)
    {
        return $this->getFactoryForType($type)->create($details);
    }

    private function getFactoryForType($type)
    {
        // Find in collection or throw exception if not supported.
    }
}

Please also keep in mind that there are languages that are completely unaware of method overloading. C, Objective-C, Rust, Ruby, Go and so on. And it is not necessarily a bad thing.

  • there are two issues here: 1. PaymentProvider implementor will not aware about details. 2. Every time create PaymentProvider for pay operation? Im' not sure it's good. When you will require to pay 1000 times or which more real — to check payment status 1000 times on different details. will you create ConcreteProvider 1000 times? – gaRex Jan 03 '16 at 19:52
  • @gaRex Ad.1. The only one who knows about the details is the implementor: PayPalProvider class is the only one that knows what details are required for it. You cannot possibly pass any arbitrary details defined by a generic interface. Ad.2. Why not? Because of performance? I don't think so. Your example is about making payment. You usually make one payment at the time. For checking payment status you need an identifier. That is all. So a completely different implementation. – Aleksander Wons Jan 03 '16 at 20:09