6

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

如果该描述有点过于抽象,这里有一些代码,其结构类似于我的代码。(我已经把它剪掉了很多,以使必要的部分变得明显,并在与整体结构无关的部分添加了一些省略号)。

我的课程如下所示:

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) = ...

现在我可以通过将所有实例放入单个模块来简单地解决这个问题,但是我有 8 个实例,而不是我的示例中显示的 2 个实例,它们都是相互递归的。最重要的是,每个实例都非常大。这意味着生成的模块将是一个巨大的无法导航的混乱。

现在,haskell wiki 有两个解决相互递归问题的建议,但它们实际上更多的是关于相互递归类型,并且它们都不会在这里工作。

无论如何,如果不简单地组合我的所有模块,是否可以绕过这种相互递归?

4

2 回答 2

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定义放在一个文件中(否则,例如,实例 for XDatacan't know that YDataimplements Foo),但至少将逻辑分成不同的模块,这就是您要寻找的。

它看起来也有点尴尬,但我想这是一个权衡。可能有办法让它变得更好。

于 2019-03-08T23:33:06.137 回答
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 允许任何简单的方法来制作实际的递归模块。)

于 2019-03-08T21:52:04.040 回答