Можно ли лениво получить все контексты "Траверсируемого"?
lens
предлагает holesOf
, который является несколько более общей и мощной версией этой гипотетической функции:
holesList :: Traversable t
=> t a -> [(a, a -> t a)]
С учетом контейнера, holesList
создает список элементов контейнера вместе с функциями для замены этих элементов.
Тип holesList
, как и у реальных holesOf
, не позволяет зафиксировать тот факт, что количество произведенных пар будет равно количеству элементов контейнера. Таким образом, гораздо более красивый тип был бы
holes :: Traversable t
=> t a -> t (a, a -> t a)
Мы могли бы реализовать holes
, используя holesList
holes
, чтобы составить список, а затем пересечься в State
чтобы сломать элементы обратно. Но это неудовлетворительно по двум причинам, одно из которых имеет практические последствия:
-
Код разрыва будет иметь недостижимый вызов ошибки для обработки случая, когда список пуст до завершения обхода. Это отвратительно, но, вероятно, не имеет большого значения для тех, кто использует эту функцию.
-
Контейнеры, которые простираются бесконечно влево или внизу слева, не будут работать вообще. Контейнеры, которые простираются очень далеко влево, будут очень неэффективными для обработки.
Мне интересно, есть ли способ обойти эти проблемы. Вполне возможно захватить форму обхода с помощью чего-то вроде Magma
в объективе:
data FT a r where
Pure :: r -> FT a r
Single :: a -> FT a a
Map :: (r -> s) -> FT a r -> FT a s
Ap :: FT a (r -> s) -> FT a r -> FT a s
instance Functor (FT a) where
fmap = Map
instance Applicative (FT a) where
pure = Pure
(<*>) = Ap
runFT :: FT a t -> t
runFT (Pure t) = t
runFT (Single a) = a
runFT (Map f x) = f (runFT x)
runFT (Ap fs xs) = runFT fs (runFT xs)
Теперь у нас есть
runFT . traverse Single = id
traverse Single
делает дерево полным элементов вместе с функциями приложений, необходимых для их сборки в контейнер. Если мы заменим элемент в дереве, мы можем runFT
результат, чтобы получить контейнер с замененным элементом. К сожалению, я застрял: я не знаю, как выглядит следующий шаг.
Смутные мысли: добавление другого параметра типа может помочь изменить типы элементов. Тип Magma
делает что-то вроде этого, и он восходит по крайней мере до тех пор, пока Zemyla комментирует сообщение в блоге Van FunList
о FunList
.
Ответы
Ответ 1
Ваше существующее решение вызывает runMag
один раз для каждой ветки в дереве, определенном конструкторами Ap
.
Я ничего не профилировал, но поскольку runMag
сам рекурсивный, это может замедлить работу на большом дереве.
Альтернативой было бы связать узел так, чтобы вы (по сути) runMag
один раз для всего дерева:
data Mag a b c where
One :: a -> Mag a b b
Pure :: c -> Mag a b c
Ap :: Mag a b (c -> d) -> Mag a b c -> Mag a b d
instance Functor (Mag a b) where
fmap = Ap . Pure
instance Applicative (Mag a b) where
pure = Pure
(<*>) = Ap
holes :: forall t a. Traversable t => t a -> t (a, a -> t a)
holes = \t ->
let m :: Mag a b (t b)
m = traverse One t
in fst $ go id m m
where
go :: (x -> y)
-> Mag a (a, a -> y) z
-> Mag a a x
-> (z, x)
go f (One a) (One _) = ((a, f), a)
go _ (Pure z) (Pure x) = (z, x)
go f (Ap mg mi) (Ap mh mj) =
let ~(g, h) = go (f . ($j)) mg mh
~(i, j) = go (f . h ) mi mj
in (g i, h j)
go _ _ _ = error "only called with same value twice, constructors must match"
Ответ 2
Мне не удалось найти действительно красивый способ сделать это. Это может быть потому, что я недостаточно умен, но я подозреваю, что это неотъемлемое ограничение типа traverse
. Но я нашел способ, который только немного уродлив! Ключ действительно кажется дополнительным аргументом типа, который использует Magma
, что дает нам свободу строить структуру, ожидающую определенного типа элемента, а затем заполнять элементы позже.
data Mag a b t where
Pure :: t -> Mag a b t
Map :: (x -> t) -> Mag a b x -> Mag a b t
Ap :: Mag a b (t -> u) -> Mag a b t -> Mag a b u
One :: a -> Mag a b b
instance Functor (Mag a b) where
fmap = Map
instance Applicative (Mag a b) where
pure = Pure
(<*>) = Ap
-- We only ever call this with id, so the extra generality
-- may be silly.
runMag :: forall a b t. (a -> b) -> Mag a b t -> t
runMag f = go
where
go :: forall u. Mag a b u -> u
go (Pure t) = t
go (One a) = f a
go (Map f x) = f (go x)
go (Ap fs xs) = go fs (go xs)
Мы рекурсивно спускаем значение типа Mag x (a, a → ta) (t (a, a → ta))
параллельно с одним из типов Mag aa (ta)
используя последнее, чтобы получить a
и a → ta
и первое в качестве основы для построения t (a, a → t)
из этих значений. x
на самом деле будет; a
он оставил полиморфный, чтобы сделать "тип тетрис" немного менее запутанным.
-- Precondition: the arguments should actually be the same;
-- only their types will differ. This justifies the impossibility
-- of non-matching constructors.
smash :: forall a x t u.
Mag x (a, a -> t) u
-> Mag a a t
-> u
smash = go id
where
go :: forall r b.
(r -> t)
-> Mag x (a, a -> t) b
-> Mag a a r
-> b
go f (Pure x) _ = x
go f (One x) (One y) = (y, f)
go f (Map g x) (Map h y) = g (go (f . h) x y)
go f (Ap fs xs) (Ap gs ys) =
(go (f . ($ runMag id ys)) fs gs)
(go (f . runMag id gs) xs ys)
go _ _ _ = error "Impossible!"
Мы фактически производим оба значения Mag
(разных типов!), Используя один вызов для traverse
. Эти два значения будут фактически представлены одной структурой в памяти.
holes :: forall t a. Traversable t => t a -> t (a, a -> t a)
holes t = smash mag mag
where
mag :: Mag a b (t b)
mag = traverse One t
Теперь мы можем играть с интересными значениями, такими как
holes (Reverse [1..])
где Reverse
- из Data.Functor.Reverse
.
Ответ 3
Вот краткая, полная (если вы игнорируете округлость) реализацию, не использует промежуточных структур данных и ленива (работает на любом бесконечном пересечении):
import Control.Applicative
import Data.Traversable
holes :: Traversable t => t a -> t (a, a -> t a)
holes t = flip runKA id $ for t $ \a ->
KA $ \k ->
let f a' = fst <$> k (a', f)
in (a, f)
newtype KA r a = KA { runKA :: (a -> r) -> a }
instance Functor (KA r) where fmap f a = pure f <*> a
instance Applicative (KA r) where
pure a = KA (\_ -> a)
liftA2 f (KA ka) (KA kb) = KA $ \cr ->
let
a = ka ar
b = kb br
ar a' = cr $ f a' b
br b' = cr $ f a b'
in f a b
KA
является "ленивым продолжением аппликативного функтора". Если мы заменим его на стандартную Cont
, мы также получим рабочее решение, которое не является ленивым:
import Control.Monad.Cont
import Data.Traversable
holes :: Traversable t => t a -> t (a, a -> t a)
holes t = flip runCont id $ for t $ \a ->
cont $ \k ->
let f a' = fst <$> k (a', f)
in k (a, f)
Ответ 4
Это на самом деле не отвечает на исходный вопрос, но он показывает другой угол. Похоже, этот вопрос на самом деле очень привязан к предыдущему вопросу, который я задал. Предположим, что у Traversable
был дополнительный метод:
traverse2 :: Biapplicative f
=> (a -> f b c) -> t a -> f (t b) (t c)
Примечание. Этот метод действительно может быть реализован на законных основаниях для любого конкретного типа данных Traversable
. Для странностей, подобных
newtype T a = T (forall f b. Applicative f => (a -> f b) -> f (T b))
см. незаконные пути ответов на связанный вопрос.
С этим на месте мы можем спроектировать тип, очень похожий на римский, но с завихрением от пандиона:
newtype Holes t m x = Holes { runHoles :: (x -> t) -> (m, x) }
instance Bifunctor (Holes t) where
bimap f g xs = Holes $ \xt ->
let
(qf, qv) = runHoles xs (xt . g)
in (f qf, g qv)
instance Biapplicative (Holes t) where
bipure x y = Holes $ \_ -> (x, y)
fs <<*>> xs = Holes $ \xt ->
let
(pf, pv) = runHoles fs (\cd -> xt (cd qv))
(qf, qv) = runHoles xs (\c -> xt (pv c))
in (pf qf, pv qv)
Теперь все мертво просто:
holedOne :: a -> Holes (t a) (a, a -> t a) a
holedOne x = Holes $ \xt -> ((x, xt), x)
holed :: Traversable t => t a -> t (a, a -> t a)
holed xs = fst (runHoles (traverse2 holedOne xs) id)