Так как никто не ответил на вопрос, я думаю, что сам попробую. Я собираюсь стать немного философским.
Универсальное программирование - это абстрагирование схожих типов без потери информации о типах (что происходит с объектно-ориентированным полиморфизмом значений). Для этого типы должны обязательно иметь некоторый интерфейс (набор операций, а не термин OO), который вы можете использовать.
В объектно-ориентированных языках типы удовлетворяют интерфейсу благодаря классам. Каждый класс имеет свой собственный интерфейс, определенный как часть его типа. Поскольку все классы List<T>
имеют одинаковый интерфейс, вы можете написать код, который работает независимо от того, какой T
вы выберете. Еще один способ навязывания интерфейса - это ограничение наследования, и хотя оба они кажутся разными, они похожи, если подумать.
В большинстве объектно-ориентированных языков List<>
сам по себе не является правильным типом. У него нет методов и, следовательно, нет интерфейса. Это только List<T>
то, что имеет эти вещи. По сути, в более технических терминах единственные типы, которые вы можете осмысленно обобщать, - это типы с таким типом *
. Чтобы использовать типы с более высоким родом в объектно-ориентированном мире, вы должны сформулировать ограничения типов так, чтобы это соответствовало этому ограничению.
Например, как упоминалось в комментариях, мы можем рассматривать Option<>
и List<>
как «отображаемые», в том смысле, что если у вас есть функция, вы можете преобразовать Option<T>
в или Option<S>
, или List<T>
в List<S>
. Помня, что классы нельзя использовать для абстрагирования над типами с более высоким родом, мы вместо этого создаем интерфейс:
IMappable<K<_>, T> where K<T> : IMappable<K<_>, T>
И тогда мы реализуем интерфейс в обоих List<T>
и Option<T>
как IMappable<List<_>, T>
и IMappable<Option<_>, T>
соответственно. То, что мы сделали, - это использование типов с более высоким родом, чтобы наложить ограничения на фактические (не выше) типы Option<T>
и List<T>
. Вот как это делается в Scala, хотя, конечно, в Scala есть такие функции, как черты, переменные типа и неявные параметры, которые делают его более выразительным.
В других языках можно абстрагироваться над типами с более высоким родом. В Haskell, одном из высших авторитетов в системах типов, мы можем сформулировать класс типов для любого типа, даже если он имеет более высокий тип. Например,
class Mappable mp where
map :: mp a -> mp b
Это ограничение, накладываемое непосредственно на (неуказанный) тип mp
который принимает один параметр типа и требует, чтобы он был связан с функцией, map
которая превращает a mp<a>
в mp<b>
. Затем мы можем написать функции, которые ограничивают типы с более высоким родом, Mappable
точно так же, как в объектно-ориентированных языках вы можете поместить ограничение наследования. Ну вроде.
Подводя итог, ваша способность использовать типы с более высоким родом зависит от вашей способности ограничивать их или использовать их как часть ограничений типов.