Зачем вам нужны более высокие виды?

13

Некоторые языки допускают классы и функции с параметрами типа (например, 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<_>, которые не имеют общей функциональности вообще.

GregRos
источник
7
Здесь есть несколько хороших ответов: stackoverflow.com/questions/21170493/…
itsbruce
3
@itsbruce В частности, Functorпример из ответа Луиса Касильяс довольно интуитивен. Что общего List<T>и Option<T>общего? Если вы дадите мне одну или другую функцию, T -> Sя могу дать вам List<S>или Option<S>. Еще одна общая черта - вы можете попытаться Tизвлечь выгоду из обоих.
Довал
@Doval: Как бы вы сделали первый? В той степени, в которой кто-то заинтересован в последнем, я думаю, что это можно сделать, если реализовать оба типа IReadableHolder<T>.
суперкат
@supercat Я предполагаю , что они должны были бы реализовать IMappable<K<_>, T>с помощью метода K<S> Map(Func<T, S> f), реализации , как IMappable<Option<_>, T>, IMappable<List<_>, T>. Таким образом, вам придется ограничиться, K<T> : IMappable<K<_>, T>чтобы получить какую-либо пользу от этого.
GregRos
1
Кастинг не является подходящей аналогией. Что делается с классами типов (или аналогичными конструкциями), так это то, что вы показываете, как данный тип удовлетворяет типу с более высоким родом. Обычно это включает в себя определение новых функций или показ того, какие существующие функции (или методы, если это язык OO, такой как Scala), можно использовать. Как только это будет сделано, все функции, определенные для работы с более высоким типом, будут работать с этим типом. Но это гораздо больше, чем интерфейс, потому что определяется не просто набор сигнатур функций / методов. Я думаю, мне придется пойти и написать ответ, чтобы показать, как это работает.
itbruce

Ответы:

5

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

Универсальное программирование - это абстрагирование схожих типов без потери информации о типах (что происходит с объектно-ориентированным полиморфизмом значений). Для этого типы должны обязательно иметь некоторый интерфейс (набор операций, а не термин 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точно так же, как в объектно-ориентированных языках вы можете поместить ограничение наследования. Ну вроде.

Подводя итог, ваша способность использовать типы с более высоким родом зависит от вашей способности ограничивать их или использовать их как часть ограничений типов.

GregRos
источник
Я был слишком занят сменой работы, но, наконец, нашел время написать свой собственный ответ. Я думаю, что вы были отвлечены идеей ограничений, хотя. Один существенный аспект более родственных типов состоит в том, что они позволяют определять функции / поведение, которые могут применяться к любому типу, который может быть логически показан для квалификации. Далекие от наложения ограничений на существующие типы, классы типов (одно применение типов с более высоким родом) добавляют к ним поведение.
Брюс
Я думаю, что это вопрос семантики. Класс типов определяет класс типов. Когда вы определяете такую ​​функцию, как (Mappable mp) => mp a -> mp b, вы накладываете ограничение на mpчленство в классе типов Mappable. Когда вы объявляете тип, например, Optionэкземпляром Mappable, вы добавляете поведение к этому типу. Я предполагаю, что вы можете использовать это поведение локально, не ограничивая какой-либо тип, но тогда оно ничем не отличается от определения обычной функции.
GregRos
Кроме того, я вполне уверен, что классы типов не имеют прямого отношения к типам с более высоким родом. В Scala есть типы с более высоким родом, но не классы типов, и вы можете ограничить классы типов типами, *не делая их непригодными для использования. Правда, классы типов очень эффективны при работе с типами более высокого класса.
GregRos
У Scala есть классы типов. Они реализованы с последствиями. Implicits обеспечивают эквивалент экземпляров класса типа Haskell. Это более хрупкая реализация, чем у Haskell, потому что для нее нет выделенного синтаксиса. Но это всегда было одной из ключевых целей Scala, и они делают свою работу.
Брюс