如何删除这种类型的相互递归?

时间:2019-03-08 20:40:43

标签: haskell mutual-recursion

我遇到了一个相互递归的问题。我采用的基本结构是,我有一个定义类型类的模块和几个定义该类型类实例的模块。但是,每个实例都是根据所有其他实例定义的。

如果该描述有点抽象,那么这里的一些代码的结构类似于我的代码。 (我已对其进行了相当大的削减,以使必要的部分变得明显,并在与整体结构无关的部分上添加了一些椭圆形。)

我的课程如下所示:

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 has two suggestion用于解决相互递归问题,但是它们实际上都更多地是关于相互递归类型的,它们都不在这里起作用。

总有没有简单地组合我所有模块的方法来绕过这种相互递归吗?

2 个答案:

答案 0 :(得分:1)

这是一种稍微有些古怪的方式。首先,将递归定义放在一个模块中:

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允许使用任何简单的方法来制作实际的递归模块。)

答案 1 :(得分:1)

也许您可以抽象出递归需求?像这样:

{-# 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) ,但是至少逻辑要分为不同的模块,这正是您要寻找的。

看起来也有些尴尬,但我想这是一个折衷方案。可能有一种更好的方法。