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

Написание cojoin или cobind для n-мерного типа сетки

Используя типичное определение naturals типового уровня, я определил n-мерную сетку.

{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}

data Nat = Z | S Nat

data U (n :: Nat) x where
  Point :: x -> U Z x
  Dimension :: [U n x] -> U n x -> [U n x] -> U (S n) x

dmap :: (U n x -> U m r) -> U (S n) x -> U (S m) r
dmap f (Dimension ls mid rs) = Dimension (map f ls) (f mid) (map f rs)

instance Functor (U n) where
  fmap f (Point x) = Point (f x)
  fmap f [email protected]{} = dmap (fmap f) d

Теперь я хочу сделать его экземпляром Comonad, но я не могу полностью обернуть вокруг него мозг.

class Functor w => Comonad w where
  (=>>)    :: w a -> (w a -> b) -> w b
  coreturn :: w a -> a
  cojoin   :: w a -> w (w a)

  x =>> f = fmap f (cojoin x)
  cojoin xx = xx =>> id

instance Comonad (U n) where
  coreturn (Point x) = x
  coreturn (Dimension _ mid _) = coreturn mid

  -- cojoin :: U Z x -> U Z (U Z x)
  cojoin (Point x) = Point (Point x)
  -- cojoin ::U (S n) x -> U (S n) (U (S n) x)
  cojoin [email protected]{} = undefined

  -- =>> :: U Z x -> (U Z x -> r) -> U Z r
  [email protected]{} =>> f = Point (f p)
  -- =>> :: U (S n) x -> (U (S n) x -> r) -> U (S n) r
  [email protected]{} =>> f = undefined

Используя cojoin на n-мерной сетке, будет создана n-мерная сетка n-мерных сеток. Я хотел бы предоставить экземпляр с той же идеей, что и этот, который заключается в том, что значение объединенной сетки в (x, y, z) должна быть исходная сетка, ориентированная на (x, y, z). Чтобы адаптировать этот код, нам кажется, что нам нужно reify n для выполнения n "fmaps" и n "rolls". Вам не обязательно это делать, но если это поможет, тогда вы идете.

4b9b3361

Ответ 1

Jagger/Richards: вы не всегда можете получить то, что хотите, но если вы когда-нибудь попробуете, вы сможете найти то, что вам нужно.

Курсоры в списках

Позвольте мне перестроить компоненты вашей структуры, используя snoc- и cons-lists, чтобы очистить пространственные свойства. Я определяю

data Bwd x = B0 | Bwd x :< x deriving (Functor, Foldable, Traversable, Show)
data Fwd x = F0 | x :> Fwd x deriving (Functor, Foldable, Traversable, Show)
infixl 5 :<
infixr 5 :>

data Cursor x = Cur (Bwd x) x (Fwd x) deriving (Functor, Foldable, Traversable, Show)

Пусть есть comonads

class Functor f => Comonad f where
  counit  :: f x -> x
  cojoin  :: f x -> f (f x)

и убедитесь, что курсоры - это comonads

instance Comonad Cursor where
  counit (Cur _ x _) = x
  cojoin c = Cur (lefts c) c (rights c) where
    lefts (Cur B0 _ _) = B0
    lefts (Cur (xz :< x) y ys) = lefts c :< c where c = Cur xz x (y :> ys)
    rights (Cur _ _ F0) = F0
    rights (Cur xz x (y :> ys)) = c :> rights c where c = Cur (xz :< x) y ys

Если вы включили этот материал, вы заметите, что Cursor является пространственно приятным вариантом InContext []

InContext f x = (x, ∂f x)

где ∂ принимает формальную производную функтора, давая свое понятие одноточечного контекста. InContext f всегда Comonad, как упоминалось в этом ответе, и то, что мы имеем здесь, это просто Comonad, индуцированное дифференциальной структурой, где counit извлекает элемент в фокусе, а cojoin украшает каждый элемент своим собственным контекстом, эффективно предоставляя вам контекст, полный перефокусированных курсоров, и неподвижным курсором в фокусе. Возьмем пример.

> cojoin (Cur (B0 :< 1) 2 (3 :> 4 :> F0))
Cur (B0 :< Cur B0 1 (2 :> 3 :> 4 :> F0))
    (Cur (B0 :< 1) 2 (3 :> 4 :> F0))
    (  Cur (B0 :< 1 :< 2) 3 (4 :> F0)
    :> Cur (B0 :< 1 :< 2 :< 3) 4 F0
    :> F0)

См? 2 в фокусе были украшены, чтобы стать курсором на 2; слева, у нас есть список курсора-на-1; справа, список курсора-at-3 и курсор-4.

Составляющие курсоры, транспонирование курсоров?

Теперь структура, которую вы просите быть Comonad, представляет собой n-кратный состав Cursor. Пусть <

newtype (:.:) f g x = C {unC :: f (g x)} deriving Show

Чтобы убедить comonads f и g составить, counit составить аккуратно, но вам нужен "дистрибутивный закон"

transpose :: f (g x) -> g (f x)

чтобы вы могли сделать композитный cojoin следующим образом

f (g x)
  -(fmap cojoin)->
f (g (g x))
  -cojoin->
f (f (g (g x)))
  -(fmap transpose)->
f (g (f (g x)))

Какие законы должны удовлетворять transpose? Возможно, что-то вроде

counit . transpose = fmap counit
cojoin . transpose = fmap transpose . transpose . fmap cojoin

или что бы это ни потребовалось, чтобы гарантировать, что любые два способа shoogle некоторые последовательности из f и g из одного порядка в другой дают тот же результат.

Можно ли определить transpose для Cursor с собой? Один из способов получить какую-то перестановку дешево - это отметить, что Bwd и Fwd zippily applyative, следовательно, так Cursor.

instance Applicative Bwd where
  pure x = pure x :< x
  (fz :< f) <*> (sz :< s) = (fz <*> sz) :< f s
  _ <*> _ = B0

instance Applicative Fwd where
  pure x = x :> pure x
  (f :> fs) <*> (s :> ss) = f s :> (fs <*> ss)
  _ <*> _ = F0

instance Applicative Cursor where
  pure x = Cur (pure x) x (pure x)
  Cur fz f fs <*> Cur sz s ss = Cur (fz <*> sz) (f s) (fs <*> ss)

И здесь вы должны почувствовать запах крысы. Несоответствие формы приводит к усечению, и это приведет к нарушению явно желаемого свойства, что самотранспозиция является самообращением. Любой вид рваности не сохранится. Мы получаем оператор транспонирования: sequenceA, а для вполне регулярных данных все ярко и красиво.

> regularMatrixCursor
Cur (B0 :< Cur (B0 :< 1) 2 (3 :> F0))
          (Cur (B0 :< 4) 5 (6 :> F0))
          (Cur (B0 :< 7) 8 (9 :> F0) :> F0)
> sequenceA regularMatrixCursor
Cur (B0 :< Cur (B0 :< 1) 4 (7 :> F0))
          (Cur (B0 :< 2) 5 (8 :> F0))
          (Cur (B0 :< 3) 6 (9 :> F0) :> F0)

Но даже если я просто перемещу один из внутренних курсоров из выравнивания (неважно, что размеры оборваны), все идет не так.

> raggedyMatrixCursor
Cur (B0 :< Cur ((B0 :< 1) :< 2) 3 F0)
          (Cur (B0 :< 4) 5 (6 :> F0))
          (Cur (B0 :< 7) 8 (9 :> F0) :> F0)
> sequenceA raggedyMatrixCursor
Cur (B0 :< Cur (B0 :< 2) 4 (7 :> F0))
          (Cur (B0 :< 3) 5 (8 :> F0))
          F0

Когда у вас есть одна внешняя позиция курсора и несколько внутренних позиций курсора, нет транспозиции, которая будет вести себя хорошо. Самосоздание Cursor позволяет внутренним структурам оборваться относительно друг друга, поэтому no transpose, no cojoin. Вы можете, и я, определить

instance (Comonad f, Traversable f, Comonad g, Applicative g) =>
  Comonad (f :.: g) where
    counit = counit . counit . unC
    cojoin = C . fmap (fmap C . sequenceA) . cojoin . fmap cojoin . unC

но это зависит от нас, чтобы мы постоянно поддерживали внутренние структуры. Если вы согласны принять это бремя, вы можете выполнять итерацию, потому что Applicative и Traversable легко закрываются по композиции. Вот фрагменты

instance (Functor f, Functor g) => Functor (f :.: g) where
  fmap h (C fgx) = C (fmap (fmap h) fgx)

instance (Applicative f, Applicative g) => Applicative (f :.: g) where
  pure = C . pure . pure
  C f <*> C s = C (pure (<*>) <*> f <*> s)

instance (Functor f, Foldable f, Foldable g) => Foldable (f :.: g) where
  fold = fold . fmap fold . unC

instance (Traversable f, Traversable g) => Traversable (f :.: g) where
  traverse h (C fgx) = C <$> traverse (traverse h) fgx

Изменить: для полноты, вот что он делает, когда все правильно,

> cojoin (C regularMatrixCursor)
C {unC = Cur (B0 :< Cur (B0 :<
  C {unC = Cur B0 (Cur B0 1 (2 :> (3 :> F0))) (Cur B0 4 (5 :> (6 :> F0)) :> (Cur B0 7 (8 :> (9 :> F0)) :> F0))}) 
 (C {unC = Cur B0 (Cur (B0 :< 1) 2 (3 :> F0)) (Cur (B0 :< 4) 5 (6 :> F0) :> (Cur (B0 :< 7) 8 (9 :> F0) :> F0))})
 (C {unC = Cur B0 (Cur ((B0 :< 1) :< 2) 3 F0) (Cur ((B0 :< 4) :< 5) 6 F0 :> (Cur ((B0 :< 7) :< 8) 9 F0 :> F0))} :> F0))
(Cur (B0 :<
  C {unC = Cur (B0 :< Cur B0 1 (2 :> (3 :> F0))) (Cur B0 4 (5 :> (6 :> F0))) (Cur B0 7 (8 :> (9 :> F0)) :> F0)})
 (C {unC = Cur (B0 :< Cur (B0 :< 1) 2 (3 :> F0)) (Cur (B0 :< 4) 5 (6 :> F0)) (Cur (B0 :< 7) 8 (9 :> F0) :> F0)}) 
 (C {unC = Cur (B0 :< Cur ((B0 :< 1) :< 2) 3 F0) (Cur ((B0 :< 4) :< 5) 6 F0) (Cur ((B0 :< 7) :< 8) 9 F0 :> F0)} :> F0))
(Cur (B0 :<
  C {unC = Cur ((B0 :< Cur B0 1 (2 :> (3 :> F0))) :< Cur B0 4 (5 :> (6 :> F0))) (Cur B0 7 (8 :> (9 :> F0))) F0})
 (C {unC = Cur ((B0 :< Cur (B0 :< 1) 2 (3 :> F0)) :< Cur (B0 :< 4) 5 (6 :> F0)) (Cur (B0 :< 7) 8 (9 :> F0)) F0})
 (C {unC = Cur ((B0 :< Cur ((B0 :< 1) :< 2) 3 F0) :< Cur ((B0 :< 4) :< 5) 6 F0) (Cur ((B0 :< 7) :< 8) 9 F0) F0} :> F0)
:> F0)}

Хенкок-тензорный продукт

Для регулярности вам нужно нечто большее, чем состав. Вы должны уметь фиксировать понятие "f-структуры g-структур-все-одинаковой формы". Это то, что неоценимый Питер Хэнкок называет "тензорным продуктом", который я напишу f :><: g: там есть одна "внешняя" f-форма и одна "внутренняя" g-форма, общая для всех внутренних g-структур, поэтому транспозиция легко определяется и всегда самообращается. Тензор Хэнкока не удобно определить в Хаскелле, но в зависимой типизированной постановке легко сформулировать понятие "контейнера", имеющего этот тензор.

Чтобы дать вам эту идею, рассмотрим вырожденное понятие контейнера

data (:<|) s p x = s :<| (p -> x)

где мы говорим, что s - это тип "фигуры" и p тип "положений". Значение состоит из выбора формы и хранения x в каждой позиции. В зависимом случае тип позиций может зависеть от выбора формы (например, для списков форма представляет собой число (длина), и у вас есть много позиций). Эти контейнеры имеют тензорный продукт

(s :<| p) :><: (s' :<| p')  =  (s, s') :<| (p, p')

который похож на обобщенную матрицу: пара фигур дает размеры, а затем у вас есть элемент на каждой паре позиций. Вы можете сделать это отлично, когда типы p и p' зависят от значений в s и s', и это точно определение тензорного произведения контейнеров Хэнкока.

InContext для продуктов тензора

Теперь, как вы, возможно, учились в старшей школе, ∂(s :<| p) = (s, p) :<| (p-1) где p-1 - это какой-то тип с одним меньшим элементом, чем p. Подобно ∂ (sx ^ p) = (sp) * x ^ (p-1). Вы выбираете одну позицию (записываете ее в форме) и удаляете ее. Недостатком является то, что p-1 сложно использовать без зависимых типов. Но InContext выбирает позицию, не удаляя ее.

InContext (s :<| p) ~= (s, p) :<| p

Это работает так же хорошо для зависимого случая, и мы радостно получаем

InContext (f :><: g) ~= InContext f :><: InContext g

Теперь мы знаем, что InContext f всегда a Comonad, и это говорит о том, что тензорные произведения из InContext являются comonadic, потому что они сами являются InContext s. Это означает, что вы выбираете одну позицию за измерение (и это дает вам ровно одну позицию во всем этом), где раньше у нас было одно внешнее положение и много внутренних позиций. При замене композиции тензора, все работает сладко.

Функторы Naperian

Но существует подкласс Functor, для которого тензорное произведение и композиция совпадают. Это Functor f, для которых f () ~ (): т.е. В любом случае существует только одна форма, поэтому в первую очередь исключаются значения изнасилования в композициях. Эти Functor все изоморфны (p ->) для некоторого позиционного множества p, которое мы можем рассматривать как логарифм (показатель, к которому x должен быть поднят, чтобы дать f x). Соответственно, Хэнкок называет этих функторов Naperian после Джона Напира (чей призрак преследует часть Эдинбурга, где живет Хэнкок).

class Applicative f => Naperian f where
  type Log f
  project :: f x -> Log f -> x
  positions :: f (Log f)
  --- project positions = id
Функтор

A Naperian имеет логарифм, индуцирующий положение функции ионной функции a project для найденных там элементов. Функторы Naperian все zippily Applicative, с pure и <*>, соответствующими комбинаторам K и S для проекций. Также возможно построить значение, где в каждой позиции хранится такое представление самой позиции. Законы логарифмов, которые вы, возможно, помните, нравятся.

newtype Id x = Id {unId :: x} deriving Show

instance Naperian Id where
  type Log Id = ()
  project (Id x) () = x
  positions = Id ()

newtype (:*:) f g x = Pr (f x, g x) deriving Show

instance (Naperian f, Naperian g) => Naperian (f :*: g) where
  type Log (f :*: g) = Either (Log f) (Log g)
  project (Pr (fx, gx)) (Left p) = project fx p
  project (Pr (fx, gx)) (Right p) = project gx p
  positions = Pr (fmap Left positions, fmap Right positions)

Обратите внимание, что массив фиксированного размера (вектор) задается символом (Id :*: Id :*: ... :*: Id :*: One), где One - постоянный единичный функтор, логарифм которого Void. Таким образом, массив Naperian. Теперь мы также имеем

instance (Naperian f, Naperian g) => Naperian (f :.: g) where
  type Log (f :.: g) = (Log f, Log g)
  project (C fgx) (p, q) = project (project fgx p) q
  positions = C $ fmap (\ p -> fmap (p ,) positions) positions

что означает, что многомерные массивы Naperian.

Чтобы построить версию InContext f для Naperian f, просто укажите на позицию!

data Focused f x = f x :@ Log f

instance Functor f => Functor (Focused f) where
  fmap h (fx :@ p) = fmap h fx :@ p

instance Naperian f => Comonad (Focused f) where
  counit (fx :@ p) = project fx p
  cojoin (fx :@ p) = fmap (fx :@) positions :@ p

Итак, в частности, a Focused n-мерный массив действительно будет comonad. Композиция векторов является тензорным произведением n векторов, поскольку векторы Naperian. Но n-мерным массивом Focused будет n-кратное тензорное произведение, а не композиция векторов n Focused, определяющих его размеры. Чтобы выразить эту комонаду в терминах молнии, нам нужно выразить их в форме, которая позволяет построить тензорное произведение. Я оставлю это как упражнение на будущее.

Ответ 2

Еще одна попытка, вдохновленная постом свиней и http://hackage.haskell.org/packages/archive/representable-functors/3.0.0.1/doc/html/Data-Functor-Representable.html.

Представляемый (или Naperian) функтор является самим комонадом, если ключ (или журнал) является моноидом! Затем coreturn получает значение в позиции mempty. И cojoin mappend доступны два ключа. (Так же, как экземпляр comonad для (p ->).)

{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

import Data.List (genericIndex)
import Data.Monoid
import Data.Key
import Data.Functor.Representable

data Nat = Z | S Nat

data U (n :: Nat) x where
  Point :: x -> U Z x
  Dimension :: [U n x] -> U n x -> [U n x] -> U (S n) x

dmap :: (U n x -> U m r) -> U (S n) x -> U (S m) r
dmap f (Dimension ls mid rs) = Dimension (map f ls) (f mid) (map f rs)

instance Functor (U n) where
  fmap f (Point x) = Point (f x)
  fmap f [email protected]{} = dmap (fmap f) d

class Functor w => Comonad w where
  (=>>)    :: w a -> (w a -> b) -> w b
  coreturn :: w a -> a
  cojoin   :: w a -> w (w a)

  x =>> f = fmap f (cojoin x)
  cojoin xx = xx =>> id

U является представимым, если списки бесконечно длинны. Тогда есть только одна форма. Ключом U n является вектор из n целых чисел.

type instance Key (U n) = UKey n

data UKey (n :: Nat) where
  P :: UKey Z
  D :: Integer -> UKey n -> UKey (S n)

instance Lookup (U n) where lookup = lookupDefault
instance Indexable (U n) where
  index (Point x) P = x
  index (Dimension ls mid rs) (D i k) 
    | i < 0 = index (ls `genericIndex` (-i - 1)) k
    | i > 0 = index (rs `genericIndex` ( i - 1)) k
    | otherwise = index mid k

Нам нужно разбить экземпляр Representable в двух случаях: один для Z и один для S, потому что у нас нет значения типа U n для соответствия шаблону.

instance Representable (U Z) where
  tabulate f = Point (f P)
instance Representable (U n) => Representable (U (S n)) where
  tabulate f = Dimension 
    (map (\i -> tabulate (f . D (-i))) [1..]) 
    (tabulate (f . D 0))
    (map (\i -> tabulate (f . D   i)) [1..])

instance Monoid (UKey Z) where
  mempty = P
  mappend P P = P
instance Monoid (UKey n) => Monoid (UKey (S n)) where
  mempty = D 0 mempty
  mappend (D il kl) (D ir kr) = D (il + ir) (mappend kl kr)

И ключ U n действительно является моноидом, поэтому мы можем превратить U n в comonad, используя стандартные реализации из пакета представляемого-функтора.

instance (Monoid (UKey n), Representable (U n)) => Comonad (U n) where
  coreturn = extractRep
  cojoin = duplicateRep
  (=>>) = flip extendRep

На этот раз я провел некоторое тестирование.

testVal :: U (S (S Z)) Int
testVal = Dimension 
  (repeat (Dimension (repeat (Point 1)) (Point 2) (repeat (Point 3))))
          (Dimension (repeat (Point 4)) (Point 5) (repeat (Point 6)))
  (repeat (Dimension (repeat (Point 7)) (Point 8) (repeat (Point 9))))

-- Hacky Eq instance, just for testing
instance Eq x => Eq (U n x) where
  Point a == Point b = a == b
  Dimension la a ra == Dimension lb b rb = take 3 la == take 3 lb && a == b && take 3 ra == take 3 rb

instance Show x => Show (U n x) where
  show (Point x) = "(Point " ++ show x ++ ")"
  show (Dimension l a r) = "(Dimension " ++ show (take 2 l) ++ " " ++ show a ++ " " ++ show (take 2 r) ++ ")"

test = 
  coreturn (cojoin testVal) == testVal && 
  fmap coreturn (cojoin testVal) == testVal && 
  cojoin (cojoin testVal) == fmap cojoin (cojoin testVal)

Ответ 3

Таким образом, это оказывается неправильным. Я оставлю его здесь, если кто-то захочет его исправить.

Эта реализация - это способ, которым, как мне кажется, предложил @pigworker. Он компилируется, но я его не тестировал. (Я взял реализацию cojoin1 из http://blog.sigfpe.com/2006/12/evaluating-cellular-automata-is.html)

{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}

data Nat = Z | S Nat

data U (n :: Nat) x where
  Point :: x -> U Z x
  Dimension :: [U n x] -> U n x -> [U n x] -> U (S n) x

unPoint :: U Z x -> x
unPoint (Point x) = x

dmap :: (U n x -> U m r) -> U (S n) x -> U (S m) r
dmap f (Dimension ls mid rs) = Dimension (map f ls) (f mid) (map f rs)

right, left :: U (S n) x -> U (S n) x
right (Dimension a b (c:cs)) = Dimension (b:a) c cs
left  (Dimension (a:as) b c) = Dimension as a (b:c)

instance Functor (U n) where
  fmap f (Point x) = Point (f x)
  fmap f [email protected]{} = dmap (fmap f) d

class Functor w => Comonad w where
  (=>>)    :: w a -> (w a -> b) -> w b
  coreturn :: w a -> a
  cojoin   :: w a -> w (w a)

  x =>> f = fmap f (cojoin x)
  cojoin xx = xx =>> id

instance Comonad (U n) where
  coreturn (Point x) = x
  coreturn (Dimension _ mid _) = coreturn mid
  cojoin (Point x) = Point (Point x)
  cojoin [email protected]{} = fmap unlayer . unlayer . fmap dist . cojoin1 . fmap cojoin . layer $ d

dist :: U (S Z) (U n x) -> U n (U (S Z) x)
dist = layerUnder . unlayer

layerUnder :: U (S n) x -> U n (U (S Z) x)
layerUnder [email protected](Dimension _ Point{} _) = Point d
layerUnder [email protected](Dimension _ Dimension{} _) = dmap layerUnder d

unlayer :: U (S Z) (U n x) -> U (S n) x
unlayer = dmap unPoint

layer :: U (S n) x -> U (S Z) (U n x)
layer = dmap Point

cojoin1 :: U (S Z) x -> U (S Z) (U (S Z) x)
cojoin1 a = layer $ Dimension (tail $ iterate left a) a (tail $ iterate right a)