我遇到了一个相互递归的问题。我采用的基本结构是,我有一个定义类型类的模块和几个定义该类型类实例的模块。但是,每个实例都是根据所有其他实例定义的。
如果该描述有点抽象,那么这里的一些代码的结构类似于我的代码。 (我已对其进行了相当大的削减,以使必要的部分变得明显,并在与整体结构无关的部分上添加了一些椭圆形。)
我的课程如下所示:
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用于解决相互递归问题,但是它们实际上都更多地是关于相互递归类型的,它们都不在这里起作用。
总有没有简单地组合我所有模块的方法来绕过这种相互递归吗?
答案 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
) ,但是至少逻辑要分为不同的模块,这正是您要寻找的。 p>
看起来也有些尴尬,但我想这是一个折衷方案。可能有一种更好的方法。