Подтвердить что ты не робот

Семейства данных против семейств инъекционных типов

Теперь, когда у нас есть инъективные типы семейств, существует ли другой вариант использования семейств данных над семействами типов?

Глядя на прошлые вопросы StackOverflow о семействах данных, этот вопрос пару лет назад обсуждал разницу между семействами типов и семействами данных и этот ответ о случаях использования семейств данных. Оба говорят, что инъективность семейств данных является их наибольшей силой.

Глядя на docs на семейства данных, я вижу, что причина не переписывать все виды использования семейств данных с помощью семейства инъективных типов.

Например, скажем, у меня есть семейство данных (я объединил некоторые примеры из документов, чтобы попытаться сжать все функции семейств данных)

data family G a b
data instance G Int Bool = G11 Int | G12 Bool deriving (Eq)
newtype instance G () a = G21 a
data instance G [a] b where
   G31 :: c -> G [Int] b
   G32 :: G [a] Bool

Я мог бы также переписать его как

type family G a b = g | g -> a b
type instance G Int Bool = G_Int_Bool
type instance G () a = G_Unit_a a
type instance G [a] b = G_lal_b a b

data G_Int_Bool = G11 Int | G12 Bool  deriving (Eq)
newtype G_Unit_a a = G21 a
data G_lal_b a b where
   G31 :: c -> G_lal_b [Int] b
   G32 :: G_lal_b [a] Bool

Само собой разумеется, что связанные экземпляры для семейств данных соответствуют соответствующим экземплярам с семействами типов одинаковым образом. Тогда единственная оставшаяся разница в том, что в пространстве имен типов меньше объектов?

В качестве продолжения, есть ли какая-либо польза от того, что в пространстве имен типов меньше вещей? Все, что я могу придумать, это то, что это станет отладочным адом для тех, кто играет с этим на ghci - типы конструкторов все, кажется, указывают на то, что все конструкторы находятся под одним GADT...

4b9b3361

Ответ 1

type family T a = r | r -> a
data family D a

Семейство инъективных типов T удовлетворяет аксиоме инъективности

если T a ~ T b, тогда a ~ b

Но семейство данных удовлетворяет гораздо более сильной аксиоме рождаемости

если D a ~ g b, затем D ~ g и a ~ b

(Если вам нравится: поскольку экземпляры D определяют новые типы, отличные от любых существующих типов.)

Фактически D сам по себе является законным типом в системе типов, в отличие от семейства типов, такого как T, который может появляться только в полностью насыщенном приложении, таком как T a. Это означает

  • D может быть аргументом для другого конструктора типов, например MaybeT D. (MaybeT T является незаконным.)

  • Вы можете определить экземпляры для D, например instance Functor D. (Вы не можете определить экземпляры для семейства типов Functor T, и это было бы непригодным для использования, поскольку выбор экземпляра, например, map :: Functor f => (a -> b) -> f a -> f b, основан на том, что из типа f a вы можете определить как f, так и a, так как для работы f не разрешается изменять типы семейств типов, даже инъективных.)

Ответ 2

Вам не хватает еще одной детали - семейства данных создают новые типы. Типы семей могут ссылаться только на другие типы. В частности, каждый экземпляр семейства данных объявляет новые конструкторы. И это красиво родовое. Вы можете создать экземпляр данных с помощью newtype instance, если хотите семантику newtype. Ваш экземпляр может быть записью. Он может иметь несколько конструкторов. Это может быть даже GADT, если вы хотите.

Именно различие между ключевыми словами type и data/newtype. Семейства инъекционных типов не дают вам новых типов, что делает их бесполезными в случае, когда вам это нужно.

Я понимаю, откуда вы. Сначала у меня была эта же проблема с разницей. Затем я, наконец, столкнулся с прецедентом, в котором они полезны, даже без участия класса типа.

Я хотел написать api для работы с изменяемыми ячейками в нескольких разных контекстах без использования классов. Я знал, что хочу сделать это со свободной монадой с переводчиками в IO, ST и, возможно, с некоторыми ужасными хаками с unsafeCoerce, даже до тех пор, пока он не запустит его в State. Конечно, это было не для какой-либо практической цели - я просто изучал дизайн API.

Итак, у меня было что-то вроде этого:

data MutableEnv (s :: k) a ...

newRef :: a -> MutableEnv s (Ref s a)
readRef :: Ref s a -> MutableEnv s a
writeRef :: Ref s a -> a -> MutableEnv s ()

Определение MutableEnv не было важно. Просто стандартный бесплатный/операционный материал монады с конструкторами, соответствующими трем функциям в api.

Но я застрял над тем, что определить Ref как. Мне не нужен какой-то класс, я хотел, чтобы он был конкретным типом в отношении системы типов.

Тогда поздно ночью я вышел на прогулку, и это ударило меня - то, что я по существу хочу, это тип, конструкторы которого индексируются типом аргумента. Но это должно было быть открытым, в отличие от GADT - новые переводчики могли бы быть добавлены по своему усмотрению. И тут меня осенило. Именно это и есть семейство данных. Открытое, индексированное по типу семейство значений данных. Я мог бы завершить api следующим образом:

data family Ref (s :: k) :: * -> *

Тогда обращение к базовому представлению для Ref не имело большого значения. Просто создайте экземпляр данных (или экземпляр newtype, скорее), когда будет определен интерпретатор для MutableEnv.

Этот точный пример не очень полезен. Но это наглядно иллюстрирует то, что семейства данных могут делать, что семейства инъективных типов не могут.

Ответ 3

Ответ Рида Бартона объясняет различие между двумя моими примерами. Это напомнило мне что-то, что я прочитал в Ричарде Айзенберге thesis о добавлении зависимых типов к Haskell, и я подумал, что, поскольку суть этого вопроса это инъективность и генерируемость, стоит упомянуть, как DependentHaskell будет иметь дело с этим (когда он в конечном итоге будет реализован, и если предлагаемые в настоящее время кванторы являются последними). ​​

Далее следуют страницы 56 и 57 (4.3.4 Совместимость) вышеупомянутого thesis:

Определение (Generativity) . Если f и g являются генеративными, то f a ~ g b подразумевает f ~ g

Определение (Инъективность) . Если f является инъективным, то f a ~ f b подразумевает a ~ b

Определение (соответствие) . Функция f сопоставима, если она является генеративной и инъективной

В Haskell, как мы это знаем сейчас (8.0.1), сопоставимые (тип-уровни) функции состоят именно из конструкторов типа newtype, data и data family. В будущем в DependentHaskell один из новых кванторов, который мы получим, будет '->, и это будет использоваться для обозначения совместимых функций. Другими словами, будет возможность сообщить компилятору, что функция уровня типа является генеративной (которая в настоящее время может быть выполнена только при условии, что функция является конструктором типа).