Operator polymorphism

Полиморфизъм, основан на това, че една и съща операция от чисто семантична гледна точка (а не математическа), може да извършва различни действия и да създава различен краeн резултат, в зависимост от операндите.

Например под операцията събиране може се има предвид конкатениране на стрингове ако подaдем стрингове, може да знaчи аритметическо събиране ако имаме числа… и т.н…

Затова казвамe, че се има предвид семантичнният смисъл на операцията, а не чисто математическият.

Добър пример за Operator polymorphism е например Ad hoc polymorphism, по-известен като function overloading.
Koeто означа, че следователно можем да причислим Operator polymorphism към Static polymorphism.

Static and dynamic polymorphism

Още са известни като Compile time polymorphism (static binding) и Runtime polymorphism (dynamic/late binding).

Това са видове полиморфизъм според това КОГА се случват, демек compile time или runtime. В какъв смисъл? В смисъл кога изпълнението на програмата ще извика по даден сигнъчър на функцията – един код, кога друг.

Динамичните – runtime, a Статичните – compiletime.

Даже по-правилно е Static polymorphism да бъде наричан Static binding, и съответно Dynamic polymorphism – Dynamic binding.

Но какво изобщо е binding?
The process of replacement of the method call with method definition is called as binding.
Или още – като извикаме функция с дадено име, параметри и тип на резултата, коя точно функция да извикаме и изпълним.
Binding е „връзката“ между извикването и дефиницията, или по-точно, като извикаме това и това, кой код реално ще се изпълни.

Например Ad hoc (или още Overloading) се случва compile time, защото тогава програмата решава коя от всички функции с еднакво име, ще бъде извикана runtime, на база на подадените аргументи. Демек – ясно се вижда – при даден сигнъчър какво да се изпълнява. Затова е Static binding или oще Static polymorphism.
The compiler looks at the method signature and decides which method to invoke for a particular method call at compile time.

Compile time polymorphism (static binding) and Runtime polymorphism (dynamic binding). Method overloading is an example of static polymorphism, while method overriding is an example of dynamic polymorphism.

Dynamic binding (Dynamic polymorphism) имаме например при оверрайдване на методи:

class SomeParent
{
    public function sayHello(): void
    {
        echo 'I am parent';
    }
}

class SomeChild extends SomeParent
{
    public function sayHello(): void
    {
        echo 'I am child';
    }
}

(new SomeParent)->sayHello();    // I am parent
(new SomeChild)->sayHello();     // I am child

Защо имаме динамичен, а не статичен? Не е ли и в този пример зададено още compiletime кога кой метод да извика?
Защото тънкостта е, че като имаме оверрайдване все едно казваме на самият компилатор „за този метод, имам версия тук, имам версия там… коя ще ми трябва, още не знам“. Самата идея на оверрайдването е такава – да НЕ се знае предварително коя точно „версия“ на метода ще ни трябва.

Ще попитате, „добре де, така написано, компилаторът не му ли става ясно кога кой метод да извика?“
Явно не, защото докато не инстанцираме обектите, което става runtime, няма как да знаем кой точно да извикаме. Демек, той методът е един, не са два или повече, просто няма как да направим самата връзка между извикването и това кое да извикаме, докато нямаме обект-викач, който да го извика.
The version of a method that is executed will be determined by the object that is used to invoke it.
Докато няма родителски и детски обекти, няма как да знаем чия „версия“ на метода да извикаме.

method overloading is an example of compile time/static polymorphism because method binding between method call and method definition happens at compile time and it depends on the reference of the class (reference created at compile time and goes to stack).

method overriding is an example of run time/dynamic polymorphism because method binding between method call and method definition happens at run time and it depends on the object of the class (object created at runtime and goes to the heap).

Method Overloading is an example of static polymorphism.
Method Overriding is an example of dynamic polymorphism.

Static Polymorphism achieved through static binding.
Dynamic Polymorphism achieved through dynamic binding.

Static Polymorphism happens in the same class.
Dynamic Polymorphism happens between different classes.

Object assignment is not required for static polymorphism.
It is required where a subclass object is assigned to super class object for dynamic polymorphism.

Inheritance not involved for static polymorphism.
Inheritance involved for dynamic polymorphism.

Работата е там, че при оверлоудване имаме съвсем различни функции/методи но с ЕДНО ИМЕ.

При оверрайдване имаме ЕДНА функция/метод, в отделни наследяващи се класове, но просто все още не сме решили коя от тях да извикаме. А ще решим на база това кой от класовете инстанцираме или използваме статично. Демек, няма как предварително, при компилиране, компилаторът да знае коя версия на тази ЕДНА функция ще извикаме.

Демек, при оверлоудване предварително, на база сигначъра казваме на компилатора „може тази функция/метод да се казва винаги еднакво, но ако я извикаш с тези и тези аргументи и тип резултат – извикваш тази, ако я извикаш с тези и тези аргументи и тип резултат – извикваш онази.“ На база различният сигнъчър, дори и имената да са еднакви, компилаторът знае коя кое да извика предварително.
Всичко е ясно зададено предварително. На база сигнъчър.

Не е така с оверрайдването. Там коя точно версия на функцията/метода ще се извика, се случва рънтайм.

Демек, при оверрайдването идеята е да предоставим предварително N на брой „версии“ на даден метод, но чак рънтайм да решим коя точно да извикаме. Демек, байндингът да стане динамично.

Литература:

https://stackoverflow.com/questions/20783266/what-is-the-difference-between-dynamic-and-static-polymorphism-in-java

https://www.sitepoint.com/quick-guide-to-polymorphism-in-java/

https://en.wikipedia.org/wiki/Method_overriding

Subtype polymorphism

Още известен като Inclusion Polymorphism – ако имаме клас А, който бива наследен от клас B, то обекти от клас B могат да бъдат използвани вместо такива от клас А, стига разбира се да не са private.

Важи също и за примитивните типове като например int и float. Долният пример на PHP e валиден, защото int се явява като подтип на float:

function patapan(float $a) { }

patapan(123);

Да си спомним за т.н. Numeric tower – йерархично подредени числени типове. В PHP откъм числени типове имаме само Integer и Float. Float e програмният (или компютърният) начин за съхранение на рационални числа (Rational), разликата е, че по дефиниция, от математическа гледна точка рационалните числа могат да бъдат безкрайни дроби (напр. 1/3 е 0.333333…), а float’s трябва да могат все пак да съхранят тази безкрайна дроб с известно закръгляне и загуба на точност.

Coercion polymorphism

Нека първо си припомним какво е Implicit type casting в PHP.

<?php
declare(strict_types = 1);

$x = '25';
var_dump($x);       // string(2) "25"
var_dump($x * 2);   // int(50)

Виждаме как PHP сам е извършил type cast към integer за да изпълни аритметичната операция.

Explicit type casting е зададен програмно от нас, когато сами кастваме променлива към друг тип.

При Coercion polymorphism можем да използваме и двата вида type casting, като например с помоща на вторият вид (explicit), може например да кастваме връщаният резултат.

PHP поддържа Coercion polymorphism само ако зададем declare(strict_types = 0); за да разрешим на PHP функциите да приемат аргументи от различен от зададеният тип.

<?php

class SomeClass
{
    private int $a;

    public function setA(int $a): void
    {
        $this->a = $a;
        var_dump($this->a);
    }
}

$p = new SomeClass;
$p->setA(123);      // int(123)
$p->setA(123.45);   // int(123)

Ясно се вижда идеята на тoзи вид полиморфизъм – PHP сам, имплицитно преобразува подаденият аргумент и по този начин можем да използваме въпросният метод както с целочислени, така и с дробни аргументи.

Parametric polymorphism

Логиката на функцията трябва да се задава с това, какви типове аргументи и подаваме, а не да я override-ваме или overload-ваме (второто е друг тип полиморфизъм – Ad hoc).

За разлика от Ad hoc полиморфизмът, където имаме същата функция, пренаписана (overloaded) повече пъти, според това какъв тип аргументи и подаваме.

Примерно, ако не задаваш типа на входните параметри, както до скоро беше по принцип в PHP, можеш да използваш една и съща функция за например събиране на числа или конкатениране на стрингове…
Това е особено лесно например в JavaScript, където операторът „+“ служи и за двете.
Но това не е са добри примери за Parametric Polymorphism, защото изискването е да се спазва т.н. type safety.

… parametric polymorphism is a way to make a language more expressive, while still maintaining full static type-safety

Идеята на Parametric Polymorphism НЕ Е да имаме пълна либералност на това, какво подаваме като тип на параметрите, а по-скоро да разширим областта от възможни типове но така, че логиката вътре във функцията да е една и съща без значение какъв конкретно тип параметри подаваме. Под „да разширим“ се има предвид, да имаме т.н. ковариантност на входните типове.

Toест, не да можем да подаваме каквото си искаме, това би било т.н. Ad hoc plymorphism, което си е едно към едно overloading.
Following Christopher Strachey, parametric polymorphism may be contrasted with ad hoc polymorphism, in which a single polymorphic function can have a number of distinct and potentially heterogeneous implementations depending on the type of argument(s) to which it is applied. 

Защото това например НЕ Е Parametric Polymorphism:

function add($a, $b)
{
    if ((gettype($a) === 'string') && (gettype($b) === 'string')) {
        return $a . $b;
    }
    if ((gettype($a) === 'integer') && (gettype($b) === 'integer')) {
        return $a + $b;
    }
}

Нито пък това, което би било Ad hoc и го давам като неработещ пример, защото както знаем в PHP оверлоудване не се поддържа:

function add(int $a, int $b): int
{
    return $a + $b;
}

function add(string $a, string $b): string
{
    return $a . $b;
}

За PHP специално, добър пример би бил използването на SOLID принципът Dependency Inversion, при който изискваме абстракция, а не конкретна функционалност за тип на входен параметър. Тоест, подаваме на една функция различни обекти но от един и същ интерфейс или абстрактен клас, и тези подадени обекти съответно правейки различни неща, определят поведението на функцията.

interface CacheableInterface
{
    public function getCacheKey(): string;
}

class ProductClass implements CacheableInterface
{
    public function getCacheKey(string $id): string
    {
        return 'product_' . $id;
    }
}

class CategoryClass implements CacheableInterface
{
    public function getCacheKey(string $id): string
    {
        return 'category_' . $id;
    }
}

function store(CacheableInterface $cacheable): string
{
    return $cacheable->getCacheKey('ABC-120-3G');
}

Вижда се как на store() може да подаваме различни обекти, стига да са от интерфейс CacheableInterface и съответно, тези отделни обекти имат свое виждане за това, което извеждат. От там идва и различното поведение на store()

Друго, което можем да забележим, че Parametric Polymorphism доста прилича на Subtype Polymorphism, защото горният пример би бил добър и за Subtype Polymorphism ако имахме и йерархия от класове.

Добър въпрос би бил, каква е разликата между Parametric Plymorphism и Subtype Polymorphism?

При Subtype Plymorphism трябва да имаме йерархия на типовете, например на класовете или на примитивните типове (float, int…).

При Parametric Polymorphism – не. Както се вижда от горният пример, няма йерархия между взаимозаменяемите ProductClass и CategoryClass, между тях самите, нищо че имплементират общ интерфейс.

Затова Parametric Polymorphism е по-общият случай на Subtype Polymorphism.

Литература:

https://sergeyzhuk.me/2017/04/09/oop-polymorphism/

https://en.wikipedia.org/wiki/Parametric_polymorphism

Inclusion plymorphism

Същият като Subtyping Polymorphism – ако имаме клас А, който бива наследен от клас B, то обекти от клас B могат да бъдат използвани вместо такива от клас А.

Както и, можем вместо float да използваме int.

Става въпрос за типовете на подаваните аргументи.

Що е polymorphism и има ли той почва у нас?

Полиморфизъм, поне в PHP имаме, когато имаме интерфейс, задаващ схемата на класовете, които го имплементират, и във отделните класове имаме един или повече метода, които правят различни неща според класа.

Но понеже името, и изобщо signature на такива методи е общ, се създава впечатление, че е един и същ метод, вършещ различни неща според класа си. А те реално са съвсем отделни методи.

According to the Polymorphism principle, methods in different classes that do similar things should have the same name.

Ето го примерният интерфейс:

interface Shape
{
    public function calcArea(); 
}

Ако имаме клас Circle implpements Shape или Square implements Shape, или каквъвто и да е shape… то за всеки клас, методът calcArea() ще върши различна работа.

Полиморфизмът в обектно ориентираното програмиране представлява свойството на обектите от един и същи интерфейс, но с различна реализация на този интерфейс да правят различни неща.

Това важи и за абстрактните класове – общите неща са отделени в абстрактният клас, но специфичните – според конкретния случай.

Различни неща или обекти могат да имат еднакъв интерфейс или да отговарят на едно и също (по наименование) съобщение и да реагират подходящо, в зависимост от природата или типа на обекта. Това позволява много различни неща да бъдат взаимозаменими. Например, ако една птица получи съобщение „движи се“, тя ще маха с крила и ще лети. Ако един лъв получи същото съобщение, той ще тича, използвайки краката си. И двете животни отговарят на една и съща команда по начини, които са свойствени за всяко от тях. Все едно два обекта, имплементиращи общ интерфейс, имплементирайки го по свой си начин.

Три са най-разпространените форми на полиморфизъм:

Ad hoc polymorphism или още Overloading

Различна имплементация на функция, на база на това какъв тип аргументи и се подава. Още е известен като „function overloading“ или „operator overloading“. Демек, това си е точно оверлоудване на функция. Ad hoc се използва, когато имаме например функции с едно име но различни имплементации, всеки за конкретен случай, и явно затова се казва ad hoc – „специално за“, „само за“.

program Adhoc;

function Add(x, y : Integer) : Integer;
begin
    Add := x + y
end;

function Add(s, t : String) : String;
begin
    Add := Concat(s, t)
end;

begin
    Writeln(Add(1, 2));                     (* Prints "3"               *)
    Writeln(Add('Hello, ', 'Mammals!'));    (* Prints "Hello, Mammals!" *)
end.

Subtyping

Тук се използва идеята, че чисто логически може типовете входни параметри и изходни резултати да се организират йерархично. Как програмно става това? Например, спазвайки SOLID принципа „Dependency Inversion Principle“, можем да използваме така да се каже по-общ тип, а не конкретен. Същата идея може да се реализира и с използването на абстрактен клас като тип.

Какво изобщо е „subtyping“? Помним „Liskov substitution principle“, e това е същото, но не за функции, а за типове. Демек, „родителският“ тип трябва да може да замества „детският“, бидейки негово „обобщение“. Както и „детският“ да замества „родителският“ бидейки негов „частен случай“.
Така се получава, че даден обект може да е от повече от един тип, като тези типове са йерархично организирани.

Идеята е да се даде по-широко поле на възможният тип, който може да се приема, но все пак да има някаква йерархия.