Как я могу использовать этот тип со схемой рекурсии вместо явной рекурсии?

Рассмотрим этот код:

import Data.Maybe (fromMaybe)

data MyStructure = Foo Int | Bar String MyStructure | Baz MyStructure MyStructure | Qux Bool Bool MyStructure MyStructure deriving(Eq,Show)

makeReplacements :: [(MyStructure, MyStructure)] -> MyStructure -> MyStructure
makeReplacements replacements structure = fromMaybe (descend structure) (lookup structure replacements)
  where
    descend :: MyStructure -> MyStructure
    descend (Foo x) = Foo x
    descend (Bar x y) = Bar x (makeReplacements replacements y)
    descend (Baz x y) = Baz (makeReplacements replacements x) (makeReplacements replacements y)
    descend (Qux x y z w) = Qux x y (makeReplacements replacements z) (makeReplacements replacements w)

Он определяет рекурсивный тип данных и функцию, которая выполняет поиск и замену, просматривая его. Однако я использую явную рекурсию и вместо этого хотел бы использовать схему рекурсии.

Сначала я добавил makeBaseFunctor ''MyStructure. Для ясности я расширил получившийся Template Haskell и производный экземпляр Functor ниже. Затем я смог переписать descend:

{-# LANGUAGE DeriveTraversable, TypeFamilies #-}

import Data.Maybe (fromMaybe)
import Data.Functor.Foldable (Base, Recursive(..), Corecursive(..))

data MyStructure = Foo Int | Bar String MyStructure | Baz MyStructure MyStructure | Qux Bool Bool MyStructure MyStructure deriving(Eq,Show)

makeReplacements :: [(MyStructure, MyStructure)] -> MyStructure -> MyStructure
makeReplacements replacements structure = fromMaybe (descend structure) (lookup structure replacements)
  where
    descend :: MyStructure -> MyStructure
    descend = embed . fmap (makeReplacements replacements) . project

-- begin code that would normally be auto-generated
data MyStructureF r = FooF Int | BarF String r | BazF r r | QuxF Bool Bool r r deriving(Foldable,Traversable)

instance Functor MyStructureF where
  fmap _ (FooF x) = FooF x
  fmap f (BarF x y) = BarF x (f y)
  fmap f (BazF x y) = BazF (f x) (f y)
  fmap f (QuxF x y z w) = QuxF x y (f z) (f w)

type instance Base MyStructure = MyStructureF

instance Recursive MyStructure where
  project (Foo x) = FooF x
  project (Bar x y) = BarF x y
  project (Baz x y) = BazF x y
  project (Qux x y z w) = QuxF x y z w

instance Corecursive MyStructure where
  embed (FooF x) = Foo x
  embed (BarF x y) = Bar x y
  embed (BazF x y) = Baz x y
  embed (QuxF x y z w) = Qux x y z w
-- end code that would normally be auto-generated

Если бы я остановился на этом, я бы уже выиграл: мне больше не нужно выписывать все падежи в descend, и я не могу случайно сделать ошибку типа descend (Baz x y) = Baz x (makeReplacements replacements y) (забыв заменить внутри x). Однако здесь все еще присутствует явная рекурсия, так как я все еще использую makeReplacements из его собственного определения. Как я могу переписать это, чтобы удалить это, чтобы я выполнял всю свою рекурсию внутри схем рекурсии?


person Joseph Sible-Reinstate Monica    schedule 30.09.2019    source источник
comment
Я не уверен, что правильно следовал вашему коду, но descend мне кажется параморфизмом. Вы хотите сначала посмотреть на узел, который нужно свернуть, чтобы увидеть, следует ли его заменить, а если нет, то вы посмотрите на уже рекурсивно свернутый результат, который даст вам катаморфизм. Имеет ли подпись para, специализированный для ваших типов, выглядит многообещающе?   -  person amalloy    schedule 01.10.2019
comment
@amalloy para это (Base t (t, a) -> a) -> t -> a. Для меня это выглядит близко, но не совсем идеально. Разве я не хотел бы на самом деле ((t, Base t a) -> a) -> t -> a или ((t, Base t (t, a)) -> a) -> t -> a, чтобы я мог смотреть на элемент, на котором я нахожусь?   -  person Joseph Sible-Reinstate Monica    schedule 01.10.2019


Ответы (2)


Я нашел решение, которым вполне доволен: апоморфизм.

makeReplacements replacements = apo coalg
  where
    coalg :: MyStructure -> MyStructureF (Either MyStructure MyStructure)
    coalg structure = case lookup structure replacements of
      Just replacement -> Left <$> project replacement
      Nothing -> Right <$> project structure

Немного подумав об этом, я также увидел в этом симметрию, которая приводит к эквивалентному параморфизму:

makeReplacements replacements = para alg
  where
    alg :: MyStructureF (MyStructure, MyStructure) -> MyStructure
    alg structure = case lookup (embed $ fst <$> structure) replacements of
      Just replacement -> replacement
      Nothing -> embed $ snd <$> structure
person Joseph Sible-Reinstate Monica    schedule 01.10.2019
comment
apo мне кажется более подходящим. В каком-то смысле мое предположение о para, двойственном apo, было очень близко; в другом я был самым неправильным, каким только можно быть! - person amalloy; 02.10.2019

По итогам обсуждения вашего вопроса

para это (Base t (t, a) -> a) -> t -> a. Для меня это выглядит близко, но не совсем идеально. Разве я не хотел бы на самом деле ((t, Base t a) -> a) -> t -> a или ((t, Base t (t, a)) -> a) -> t -> a, чтобы я мог смотреть на элемент, на котором я нахожусь?

Это все еще параморфизм. Тип para выглядит странно, но он более точный. Пара (t, Base t a) не кодирует инвариант, согласно которому оба компонента всегда будут иметь «один и тот же» конструктор.

То, что вы предлагаете, по-прежнему кажется наиболее естественным способом определения makeReplacements, просто оно не определено в библиотеке схем рекурсии.

para' :: Recursive t => (t -> Base t a -> a) -> t -> a
para' alg = go where
  go x = alg x (fmap go (project x))
person Li-yao Xia    schedule 01.10.2019
comment
Возможно, было бы более информативно реализовать para' в терминах para, используя fmap fst и fmap snd? - person HTNW; 01.10.2019
comment
@HTNW Возможно ли это без добавления ограничения Corecursive? Без этого вы можете получить Base t t, но не t, который вам нужен. - person Joseph Sible-Reinstate Monica; 01.10.2019