Как удалить этот тип взаимной рекурсии? - PullRequest
6 голосов
/ 08 марта 2019

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

Если это описание слишком абстрактное, то здесь приведен код, имеющий структуру, подобную моему коду.(Я немного урезал его, чтобы сделать необходимые биты очевидными, и добавил несколько эллипсов к частям, которые не имеют отношения к общей структуре).

Мой класс выглядит следующим образом:

data Result = ...

class Foo a where
  openFoo  :: Result      -> IO (a, Result)
  runFoo   :: (a, Result) -> IO (a, Result)
  closeFoo :: (a, Result) -> IO Result

Тогда у меня есть экземпляры

data XData = ...

instance Foo XData where
   openFoo result = ...
   runFoo (data, result) = do
     currentEvent <- getEvent
     case currentEvent of
       EventA -> return (data, result)
       EventB ->
         (openFoo result :: IO YData)
           >>= runFoo
             >>= closeFoo
               >>= curry return data
   closeFoo (data, result) = ...

data YData = ...

instance Foo YData where
   openFoo result = ...
   runFoo (data, result) = do
     currentEvent <- getEvent
     case currentEvent of
       EventA -> return (data, result)
       EventB ->
         (openFoo result :: IO XData)
           >>= runFoo
             >>= closeFoo
               >>= curry return data
   closeFoo (data, result) = ...

Теперь я мог бы просто решить эту проблему, поместив все мои экземпляры в один модуль, однако вместо 2, показанного в моем примереУ меня есть 8 случаев, которые все взаимно рекурсивны друг с другом.Кроме того, каждый экземпляр довольно большой.Это означает, что полученный модуль будет огромным неуязвимым беспорядком.

Теперь в haskell wiki есть два предложения для решения вопросов взаимной рекурсии, но оба они на самом деле больше относятся к взаимно рекурсивным типам и ни к одному изони будут работать здесь.

Есть ли способ обойти эту взаимную рекурсию без простого объединения всех моих модулей?

Ответы [ 2 ]

1 голос
/ 09 марта 2019

Возможно, вы могли бы абстрагировать рекурсивное требование?Примерно так:

{-# LANGUAGE ScopedTypeVariables #-}

runFooX :: forall ydata. Foo ydata => Proxy ydata -> (XData, Result) -> IO (XData, Result)
runFooX _ (data, result) = do
  currentEvent <- getEvent
  case currentEvent of
    EventA -> return (data, result)
    EventB ->
      (openFoo result :: IO ydata)
        >>= runFoo
          >>= closeFoo
            >>= curry return data

И в отдельном файле:

instance Foo XData where
   openFoo result = ...
   runFoo = runFooX (Proxy :: Proxy YData)
   closeFoo (data, result) = ...

Таким образом, ваша файловая структура может выглядеть примерно так:

            +-----------+
            | class Foo |
            +-----------+
              /       \
             v         v
+---------------+   +---------------+
| data XData    |   | data YData    |
| runFooX = ... |   | runFooY = ... |
+---------------+   +---------------+
              |       |
              v       v
       +---------------------+
       | instance Foo XData  |
       | instance Foo YData  |
       +---------------------+

Вывсе еще нужно поместить все определения instance в один файл (в противном случае, например, экземпляр для XData не может знать, что YData реализует Foo), но по крайней мере логика разделена на разные модули,это то, что вы ищете.

Это также выглядит немного неловко, но я думаю, что это компромисс.Там может быть способ сделать это лучше.

1 голос
/ 09 марта 2019

Вот один слегка хакерский способ сделать это.Сначала поместите ваши рекурсивные определения в один модуль:

module Internal.Recursive

data XData = ...
data YData = ...

-- Recursive definitions...

Затем повторно экспортируйте каждое определение из отдельного модуля:

module XData (IR.XData) where

import qualified Internal.Recursive as IR

module YData (IR.XYata) where

import qualified Internal.Recursive as IR

Это создаст видвзаимно рекурсивных модулей.(Я не верю, что GHC позволяет любой простой способ создания реальных рекурсивных модулей.)

...