Как привести словесный пример полиморфизма?

Недавно проходил собеседование по телефону. Задали вопрос что такое полиморфизм, для чего он нужен и попросили привести пример...
Если с определением полиморфизма у меня проблем не возникло, то вопрос для чего он нужен поставил меня в тупик...
Для сокращения кода, для динамической смены реализации... ммм... Для чего?
А с примером вообще произошел конфуз. Стал приводить академический пример про геометрические фигуры и понял что объясняю крайне не понятно и не "гладко".
После собеседования, понятное дело стал искать нормальный "словесный" пример полиморфизма, а так же для чего он нужен... но нашел только примеры с отрывками кода. Но по телефону код не приведешь и не напишешь..

Так вот вопрос как привести словесный пример полиморфизма и объяснить "для чего он нужен", что бы он устроил человека на том конце провода?
  • Вопрос задан
  • 1757 просмотров
Пригласить эксперта
Ответы на вопрос 3
@kttotto
пофиг на чем писать
Когда Вам нужно что-то записать, обычно берут листочек и ручку. А можно не ручку, можно карандаш, можно фломастер или перьевую ручку. Полиморфизм будет заключаться в том, что Вам нужен объект, которым можно что-то записать, а какой конкретно и как он реализует саму запись Вас не сильно беспокоит. Т.е. когда Вы скажите: "дай мне объект для записи", Вам можно будет подсунуть любой объект, который как абстракция будет является "перо".

Когда Вы хотите спать, Вы говорите: "дай мне на что можно лечь". И можно будет лечь хоть на диван, хоть на кровать, хоть на кресло-кровать. Абстрактно это все будет "кровать", но реализацию будет иметь разную.

Полиморфиз удобен когда например могут быть изменения, дополнения в коде. Тогда мы абстрагируемся от конкретной реализации, создаем абстрактный класс и в метод уже запрашиваем объект этого типа. А во время выполнения мы может подсунуть для выполнения объект любого другого типа, который будет отнаследован от этого абстрактного и будет иметь свою реализацию каких то методов. Так что да, это "динамическая смена реализации".
Ответ написан
Комментировать
Полиморфизм бывает трёх типов: параметрический, ad-hoc и полморфизм подтипов.

Параметрический полиморфизм нужен чтобы писать логику, параметризованную типами, реализующими некоторые интерфейсы (вырожденный случай - параметризованные любыми типами). Простейший пример: функция map, применяющая функцию к каждому элементу массива. Без полиморфизма мы бы писали отдельную функцию для каждого типа:

mapIntFloat :: (Int -> Float) -> [Int] -> [Float]
mapIntFloat f [] = []
mapIntFloat f (x:xs) = (f x) : (map f xs)

mapIntString :: (Int -> String) -> [Int] -> [String]
mapIntString f [] = []
mapIntString f (x:xs) = (f x) : (map f xs)
-- и так далее


Выходит довольно громоздко и налицо повторение логики в каждой функции вида mapTypeType. Чтобы избежать повторения логики, параметризуем функцию map относительно типов исходного массива и массива, получаемого в результате:

map :: forall a b. (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = (f x) : (map f xs)


Теперь вместо конкретных типов вроде Int или Float у нас переменные типов: a и b.

Окей, но если копнуть глубже, оказывается, что логика, похожая на map для массивов, применима и к другим типам. Например, к optional-значениям. Без ad-hoc полиморфизма мы напишем что-то типа:

mapList :: forall a b. (a -> b) -> [a] -> [b]
mapList f [] = []
mapList f (x:xs) = (f x) : (mapList f xs)

mapMaybe :: forall a b. (a -> b) -> Maybe a -> Maybe b
mapMaybe f Nothing = Nothing
mapMaybe f (Just x) = Just (f x)


Видно, что логика везде примерно одна и та же, сигнатуры совпадают с точностью до параметризванного типа. Вот для этих случаев и нужен ad-hoc полиморфизм:

class  Functor f  where
    map :: (a -> b) -> f a -> f b


Мы параметризовали нужную нам логику относительно параметризованного типа f и теперь можем писать реализации для конкретных типов:

instance  Functor Maybe  where
    map f Nothing = Nothing
    map f (Just x) = Just (f x)

instance  Functor [] where
    map f [] = []
    map f (x:xs) = (f x) : (map f xs)


Сама функция map теперь имеет тип:

map :: forall f a b. Functor f => (a -> b) -> f a -> f b


Теперь мы можем писать функции, которые работают с любыми функторами, то есть опять-таки сократить повторение логики в коде.

Наконец, полиморфизм подтипов - это несколько другой подход к предыдущей проблеме. Вместо выделения общего интерфейса мы создаём базовый тип и наследуем от него остальные. В этом случае Functor будет абстрактным классом с абстрактным методом map, от которого наследуются типы Maybe, List и т.д. В таком случае сигнатура функции, принимающей и возвращающей функтор, будет выглядеть примерно так: foo :: Functor Int -> Functor String.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы