У меня есть класс типов 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
может.
Bounded
иEq
, - это возможность сказать, когда вы находитесь на последнем элементе, чтобы снова начать итерацию с какого-то другогоgen
, что и добавляет мой ответ. Обратите внимание, что добавлениеglast
кGCylic
не требует, чтобы вы добавляли соответствующую функцию кCyclic
, если только вы не собираетесь создавать экземпляры дляK1
(что вы обязательно должны сделать, потому что это потрясающе; производный экземпляр для[T3]
может вас удивить; это удивило меня). - person Cirdec   schedule 04.04.2014undefined
в качестве прокси для типов, все, что реализуетCyclic
, должно принимать значенияundefined
, поскольку некоторые реализации могут передаватьundefined
другим. Этого можно избежать, используя вместо этогоdata Proxy a = Proxy
из помеченного пакета (hackage.haskell.org/package/tagged) и вместо этого передать(Proxy :: ...)
. Вы бы изменили наord :: Proxy a -> Int
. - person Cirdec   schedule 04.04.2014glast
просочилось в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.2014gen
. Представьте себе функциюrotIsGen :: (Eq a, Cyclic a) => a -> Bool
, гдеrotIsGen = (==gen) . rot
. - person Cirdec   schedule 04.04.2014forall a. Cyclic a => a
, потому что я хочу писать функции, полиморфные вa
. Если я не экспортируюCyclic
(поэтому новые экземпляры не могут быть записаны) и тщательно выбираю экземплярыCyclic
, я могу быть уверен, что они не являются экземплярамиEq
, поэтому таких функций, какrotIsGen
, можно избежать.gen
также вписывается в абстракцию как генератор циклической группы. - person cdk   schedule 04.04.2014rotIsGen
для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.2014K1
, как бы вы реализовалиglast
дляK1
? - person cdk   schedule 04.04.2014glast
дляK1
не является проблемой, этоrotIsEnd
дляCycle
, проблема заключается в экземпляреGCycle
для продуктов (:*:
), поскольку произведение двух циклических групп является циклической группой, если их порядки взаимно просты. math.stackexchange.com/questions/5969/ - person Cirdec   schedule 04.04.2014T3
, есть несколько других подходов, которые вы могли бы использовать. Вы можете переместитьord
на уровень типа и потребовать доказательства взаимной простотыCyclic
s на уровне типа, прежде чем выводить для них произведение. Вы можете получить экземпляры продукта только для типа, который обеспечивает доказательство на уровне типа того, что значения были получены путем вращения генератора. Вы можете изменить интерфейс циклического, чтобы произвольные значения типа нельзя было вращать. - person Cirdec   schedule 06.04.2014