Обобщающие параметры класса

Допустим, у меня есть класс:

class C a b t where
  f :: (a, b) -> (t a, t b)

Теперь с этим определением я могу определить экземпляры, скажем:

(a,b) -> (Maybe a, Maybe b)
(a,b) -> ([a], [b])

Но не для (насколько я понимаю):

(a,b) -> (a,b)
(a,b) -> ((a, a), (b, b))

Вместо этого я мог бы изменить определение своего класса следующим образом:

type family T a b x

class C a b where
  f :: (a, b) -> (T a b a, T a b b)

Что позволило бы мне сделать вышеперечисленное, но тогда я смог бы объявить только один f для каждого a и b.

По сути, я хочу иметь возможность передать семейство типов как t в исходном определении, которое неявно разрешается средством проверки типов, если оно известно. Я не хочу просто сделать его f :: (a, b) -> (c, d), поскольку я хочу сохранить инвариант, согласно которому и a, и b делают с ними одно и то же, поэтому swap . f относится к тому же типу, что и f . swap. Я думаю, что мне могут понадобиться семейства инъективных типов (из GHC 8.0), но я не уверен. Но может есть другой способ?


person Clinton    schedule 23.09.2015    source источник
comment
Неответ: вы можете использовать Identity в первом случае и подходящий новый тип во втором (сейчас я не могу придумать ни одного стандартного). Это грубый обходной путь, но он может оказаться менее болезненным, чем продвинутый обман, который, как кажется, требуется. Между прочим, я не хочу просто делать это f :: (a, b) -> (c, d), так как я хочу сохранить инвариант... очень похоже на то, что линзы lens являются Lens s t a b несмотря на то, что эти четыре параметра взаимозависимы (см. раздел Почему это семейство линз?).   -  person duplode    schedule 23.09.2015
comment
Я думаю, что это в основном тот же вопрос, что и stackoverflow.com/questions/4922560/   -  person Reid Barton    schedule 23.09.2015


Ответы (1)


Это может или не может ответить на ваш вопрос в зависимости от того, что вам действительно нужно сделать.

Стандартным решением является использование нового типа для определения новых экземпляров. Например.

newtype Pair a = Pair (a, a)
instance C a b Pair where
   f = ...

Однако это приведет к тому, что f будет иметь тип

f :: (a, b) -> (Pair a, Pair b)

вместо разыскиваемого

f :: (a, b) -> ((a, a), (b, b))

Последнюю можно восстановить скучным способом:

f' :: (a, b) -> ((a, a), (b, b))
f' p = case f p of (Pair x, Pair y) -> (x, y)

Теперь написание «адаптерных» функций, таких как f', кажется излишним: в конце концов, мы просто удаляем оболочку newtype, которая не меняет представления во время выполнения. Хуже того, это может быть неэффективно: рассмотрите возможность преобразования [Pair a] в [(a, a)]. Чтобы сделать это, нам нужно отобразить весь список, поэтому мы платим O(n) и фактически ничего не делаем.

Эффективная альтернатива может быть построена с использованием безопасного приведения:

import Data.Coerce

f'' :: forall a b. (a, b) -> ((a, a), (b, b))
f'' = coerce (f :: (a, b) -> (Pair a, Pair b))

Это позволяет использовать newtype для управления выбором экземпляра и удалять их, когда они мешают.

Теперь, если ваша цель действительно состоит в том, чтобы определить новые экземпляры без newtypes, это мало поможет. Если вместо этого вам нужен простой способ удалить обертки newtypes из методов экземпляра, это может помочь.

person chi    schedule 23.09.2015
comment
Data.Coerce выглядит очень интересно. Спасибо за этот ответ. - person Bartek Banachewicz; 23.09.2015