Получение экземпляров по умолчанию с помощью GHC.Generics

У меня есть класс типов Cyclic, для которого я хотел бы предоставить общие экземпляры.

class Cyclic g where
    gen :: g
    rot :: g -> g
    ord :: g -> Int

Учитывая тип суммы нульарных конструкторов,

data T3 = A | B | C deriving (Generic, Show)

Я хочу создать экземпляр, эквивалентный этому:

instance Cyclic T3 where
    gen   = A
    rot A = B
    rot B = C
    rot C = A
    ord _ = 3

Я пытался разработать требуемый механизм Generic вот так

{-# LANGUAGE DefaultSignatures, FlexibleContexts, ScopedTypeVariables, TypeOperators #-}

import GHC.Generics

class GCyclic f where
    ggen :: f a
    grot :: f a -> f a
    gord :: f a -> Int

instance GCyclic U1 where
    ggen   = U1
    grot _ = U1
    gord _ = 1

instance Cyclic c => GCyclic (K1 i c) where
    ggen = K1 gen
    grot (K1 a) = K1 (rot a)
    gord (K1 a) = ord a

instance GCyclic f => GCyclic (M1 i c f) where
    ggen = M1 ggen
    grot (M1 a) = M1 (grot a)
    gord (M1 a) = gord a    

instance (GCyclic f, GCyclic g) => GCyclic (f :*: g) where
    ggen = ggen :*: ggen
    grot (a :*: b) = grot a :*: grot b
    gord (a :*: b) = gord a `lcm` gord b

instance (GCyclic f, GCyclic g) => GCyclic (f :+: g) where
    ggen = L1 ggen
    -- grot is incorrect
    grot (L1 a) = L1 (grot a) 
    grot (R1 b) = R1 (grot b)
    gord _ = gord (undefined :: f a)
           + gord (undefined :: g b)

Теперь я могу предоставить реализации по умолчанию для Cyclic с помощью GCyclic:

class Cyclic g where
    gen :: g
    rot :: g -> g
    ord :: g -> Int

    default gen :: (Generic g, GCyclic (Rep g)) => g
    gen = to ggen

    default rot :: (Generic g, GCyclic (Rep g)) => g -> g
    rot = to . grot . from

    default ord :: (Generic g, GCyclic (Rep g)) => g -> Int
    ord = gord . from

но мои экземпляры GCyclic неверны. Использование T3 сверху

λ. map rot [A, B, C] -- == [B, C, A]
[A, B, C]

Понятно, почему здесь rot эквивалентно id. grot выполняет рекурсию вниз по структуре (:+:) структуры T3, пока не достигнет базового варианта grot U1 = U1.

В #haskell было предложено использовать информацию о конструкторе из M1, чтобы grot мог выбрать следующий конструктор для рекурсии, но я не уверен, как это сделать.

Можно ли сгенерировать желаемые экземпляры Cyclic с помощью GHC.Generics или какой-либо другой формы Scrap Your Boilerplate?

EDIT: я мог написать Cyclic, используя Bounded и Enum

class Cyclic g where
    gen :: g
    rot :: g -> g
    ord :: g -> Int

    default gen :: Bounded g => g
    gen = minBound

    default rot :: (Bounded g, Enum g, Eq g) => g -> g
    rot g | g == maxBound = minBound
          | otherwise     = succ g

    default ord :: (Bounded g, Enum g) => g -> Int
    ord g = 1 + fromEnum (maxBound `asTypeOf` g)

но (как есть) это неудовлетворительно, так как требует всех Bounded, Enum и Eq. Кроме того, в некоторых случаях GHC не может автоматически получить Enum, в то время как более надежный Generic может.


person cdk    schedule 03.04.2014    source источник
comment
Возможно, это не совсем подходит для вашей проблемы, но вы можете указать разумные значения по умолчанию для этих функций, используя только Enum и Bounded. Затем все, что вам нужно сделать, это объявить экземпляр, никакой конкретной реализации не требуется. Я сейчас разговариваю по телефону, но позже могу привести пример. Я чувствую, что ваш реальный вариант использования немного сложнее.   -  person bheklilr    schedule 04.04.2014
comment
Единственное, что вам нужно от Bounded и Eq, - это возможность сказать, когда вы находитесь на последнем элементе, чтобы снова начать итерацию с какого-то другого gen, что и добавляет мой ответ. Обратите внимание, что добавление glast к GCylic не требует, чтобы вы добавляли соответствующую функцию к Cyclic, если только вы не собираетесь создавать экземпляры для K1 (что вы обязательно должны сделать, потому что это потрясающе; производный экземпляр для [T3] может вас удивить; это удивило меня).   -  person Cirdec    schedule 04.04.2014
comment
Если вы собираетесь передавать значения undefined в качестве прокси для типов, все, что реализует Cyclic, должно принимать значения undefined, поскольку некоторые реализации могут передавать undefined другим. Этого можно избежать, используя вместо этого data Proxy a = Proxy из помеченного пакета (hackage.haskell.org/package/tagged) и вместо этого передать (Proxy :: ...). Вы бы изменили на ord :: Proxy a -> Int.   -  person Cirdec    schedule 04.04.2014
comment
Я не хочу, чтобы воздержание glast просочилось в Cyclic. Я заменил gend на K1 с помощью const False и получил instance Cyclic [T3]. Результат довольно интересный, iterate rot (gen :: [T3]) -> [[], [A], [B,A], [C,B,A], [A,C,B,A] ...]. Это то, что вы имели в виду?   -  person cdk    schedule 04.04.2014
comment
Да, вот что меня на мгновение удивило.   -  person Cirdec    schedule 04.04.2014
comment
Вы уже пропускаете особую точку в цикле из-за gen. Представьте себе функцию rotIsGen :: (Eq a, Cyclic a) => a -> Bool, где rotIsGen = (==gen) . rot.   -  person Cirdec    schedule 04.04.2014
comment
Это хороший момент, но мне нужен способ получения значения типа forall a. Cyclic a => a, потому что я хочу писать функции, полиморфные в a. Если я не экспортирую Cyclic (поэтому новые экземпляры не могут быть записаны) и тщательно выбираю экземпляры Cyclic, я могу быть уверен, что они не являются экземплярами Eq, поэтому таких функций, как rotIsGen, можно избежать. gen также вписывается в абстракцию как генератор циклической группы.   -  person cdk    schedule 04.04.2014
comment
Рекурсия через rotIsGen для K1 неверна; это делает gen слишком особенным. Рассмотрим iterate rot $ (Left (True, False) :: Either (Bool, Bool) T3). Он посещает только Left (True, False) и Left (False, True), в которых всего 2 элемента. С другой стороны Left (False, False) генерирует [Left (False, False), Left (True, True), Right A, Right B, Right C, Left (False, False), ...   -  person Cirdec    schedule 04.04.2014
comment
Боюсь, я не понимаю. Я добавил экземпляр для K1, как бы вы реализовали glast для K1?   -  person cdk    schedule 04.04.2014
comment
glast для K1 не является проблемой, это rotIsEnd для Cycle, проблема заключается в экземпляре GCycle для продуктов (:*:), поскольку произведение двух циклических групп является циклической группой, если их порядки взаимно просты. math.stackexchange.com/questions/5969/   -  person Cirdec    schedule 04.04.2014
comment
В дополнение к моему более чем полному ответу, который вышел далеко за рамки получения экземпляра для T3, есть несколько других подходов, которые вы могли бы использовать. Вы можете переместить ord на уровень типа и потребовать доказательства взаимной простоты Cyclics на уровне типа, прежде чем выводить для них произведение. Вы можете получить экземпляры продукта только для типа, который обеспечивает доказательство на уровне типа того, что значения были получены путем вращения генератора. Вы можете изменить интерфейс циклического, чтобы произвольные значения типа нельзя было вращать.   -  person Cirdec    schedule 06.04.2014


Ответы (1)


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

Вы можете понять, когда перейти к другой стороне суммы конструкторов, если вы можете сказать, что то, что внутри, уже находится в последнем конструкторе, что и делают новые функции end и gend. Я не могу представить циклическую группу, для которой мы не можем определить end.

Вы можете реализовать gord для сумм, даже не просматривая значения; расширение ScopedTypeVariables помогает в этом. Я изменил сигнатуры, чтобы использовать прокси, поскольку теперь вы смешиваете undefined и пытаетесь деконструировать значение в своем коде.

import Data.Proxy

Вот класс Cyclic с end, значениями по умолчанию и Integral n (вместо предположения Int) для ord

class Cyclic g where
    gen :: g
    rot :: g -> g
    end :: g -> Bool
    ord :: Integral n => Proxy g -> n

    default gen :: (Generic g, GCyclic (Rep g)) => g
    gen = to ggen

    default rot :: (Generic g, GCyclic (Rep g)) => g -> g
    rot = to . grot . from

    default end :: (Generic g, GCyclic (Rep g)) => g -> Bool
    end = gend . from

    default ord :: (Generic g, GCyclic (Rep g), Integral n) => Proxy g -> n
    ord = gord . fmap from

И класс GCyclic и его реализации:

class GCyclic f where
    ggen :: f a
    gend :: f a -> Bool
    grot :: f a -> f a
    gord :: Integral n => Proxy (f ()) -> n

instance GCyclic U1 where
    ggen   = U1
    grot _ = U1
    gend _ = True
    gord _ = 1

instance Cyclic c => GCyclic (K1 i c) where
    ggen        = K1 gen
    grot (K1 a) = K1 (rot a)
    gend (K1 a) = end a
    gord  _     = ord (Proxy :: Proxy c)

instance GCyclic f => GCyclic (M1 i c f) where
    ggen        = M1    ggen
    grot (M1 a) = M1   (grot a)
    gend (M1 a) = gend  a
    gord  _     = gord (Proxy :: Proxy (f ()))

Я не могу не подчеркнуть, что следующее создает класс эквивалентности для нескольких циклических подгрупп произведения двух циклов. Из-за необходимости определять концы для сумм и того, что вычисления для lcm и gcm не ленивы, мы больше не можем делать такие забавные вещи, как получение циклического экземпляра для [a].

-- The product of two cyclic groups is a cyclic group iff their orders are coprime, so this shouldn't really work
instance (GCyclic f, GCyclic g) => GCyclic (f :*: g) where
    ggen           = ggen                          :*:  ggen
    grot (a :*: b) = grot  a                       :*:  grot  b
    gend (a :*: b) = gend  a                       &&   (any gend . take (gord (Proxy :: Proxy (f ())) `gcd` gord (Proxy :: Proxy (g ()))) . iterate grot) b
    gord  _        = gord (Proxy :: Proxy (f ())) `lcm` gord (Proxy :: Proxy (g ()))

instance (GCyclic f, GCyclic g) => GCyclic (f :+: g) where
    ggen        = L1 ggen
    grot (L1 a) = if gend a
                  then R1 (ggen)
                  else L1 (grot a)
    grot (R1 b) = if gend b
                  then L1 (ggen)
                  else R1 (grot b)
    gend (L1 _) = False
    gend (R1 b) = gend b
    gord  _     = gord (Proxy :: Proxy (f ())) + gord (Proxy :: Proxy (g ()))

Вот еще несколько примеров:

-- Perfectly fine instances
instance Cyclic ()
instance Cyclic Bool
instance (Cyclic a, Cyclic b) => Cyclic (Either a b)

-- Not actually possible (the product of two arbitrary cycles is a cyclic group iff they are coprime)
instance (Cyclic a, Cyclic b) => Cyclic (a, b)

-- Doesn't have a finite order, doesn't seem to be a prime transfinite number.
-- instance (Cyclic a) => Cyclic [a]

И пример кода для запуска:

typeOf :: a -> Proxy a
typeOf _ = Proxy

generate :: (Cyclic g) => Proxy g -> [g]
generate _ = go gen
    where
        go g = if end g
               then [g]
               else g : go (rot g)

main = do
    print . generate . typeOf $ A
    print . map rot . generate . typeOf $ A
    putStrLn []

    print . generate $ (Proxy :: Proxy (Either T3 Bool))
    print . map rot . generate $ (Proxy :: Proxy (Either T3 Bool))
    putStrLn []

    print . generate . typeOf $ (A, False)
    print . map rot . generate . typeOf $ (A, False)
    putStrLn []

    print . generate . typeOf $ (False, False)
    print . map rot . generate . typeOf $ (False, False)
    print . take 4 . iterate rot $ (False, True)
    putStrLn []

    print . generate $ (Proxy :: Proxy (Either () (Bool, Bool)))
    print . map rot . generate $ (Proxy :: Proxy (Either () (Bool, Bool)))
    print . take 8 . iterate rot $ (Right (False,True) :: Either () (Bool, Bool))
    putStrLn []

Четвертый и пятый примеры показывают, что происходит, когда мы создаем пример для произведения двух циклических групп, порядки которых не взаимно просты.

person Cirdec    schedule 04.04.2014
comment
Ой, я неправильно понял, что вы ищете. Ты gord уже должен был стать моим gsize. - person Cirdec; 04.04.2014
comment
В чем конкретно вы не уверены в отношении продуктов? Пример Cyclic для (:*:) прост (если вы знаете основы алгебры): f :*: g эквивалентно прямому произведению циклических групп f, g. - person cdk; 04.04.2014
comment
Я понял это, когда собрал ord == size и увидел, что вы реализовали его как lcm. - person Cirdec; 04.04.2014