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