我在模块evalExpression :: Exp -> Value
中有一个函数A
,它在很大程度上依赖于参数Exp
上的模式匹配。
该文件已经变得足够大,需要更多的组织。我想将模块A
拆分为模块A.GHC.Num
,A.GHC.Types
等。
或
或
A.evalExp
尝试(在try和catch的意义上)返回A.GHC.Num.evalExp
的值,如果它缓存错误(非详尽模式匹配),尝试返回A.GHC.Types.evalExp
,等等?更新:
我尝试解决循环依赖,但GHC不相信,它说“模糊发生”。
答案 0 :(得分:5)
不,您不能在多个文件之间拆分模块,并且您当然无法在不同位置定义功能。与此最接近的是一个函数,它是类型类的一部分,实例在各种模块中定义。但这可能不是你想要的。
但是, 可以编译相互递归的模块。从理论上讲,这应该是Just Work(tm),但是GHC需要一点箍跳来做它;有关详细信息,请参阅the User's Guide。如果您收到循环模块导入错误,则应该让该版本正常工作。
没有“好的”方法可以捕获无穷无尽的模式匹配错误并尝试其他方法。有许多不那么好的方法,但你可能不想去那里。
如果您的目标是在具有大量案例的单个数据类型上进行模式匹配,那么在不混淆相互递归模块或类型类的情况下最简单的分解方法是在其他模块中具有单独的函数来获取内容将每个构造函数作为直接参数,然后对导入其他模块并执行调度的模块中的所有情况进行单个模式匹配。
假设您的类型Foo
包含案例A
,B
和& c。,具有类似命名的模块。在“中央”模块中,您可以:
doStuff (A x y) = A.doStuffA x y
doStuff (B z) = B.doStuffB z
......等等。
在某些情况下,以类似的方式分割整个数据类型甚至是有意义的,并为每个构造函数创建一个单独的类型,例如:data Foo = A FooA | B FooB | ...
。当你有一个复杂的数据类型混乱时,这是最有用的,这些数据类型可能以多种方式相互递归,典型的例子是AST。
好的,这是一种模拟你想要的东西而不做任何粗略的事情。
首先,按照您理想的方式将您的功能分成不同的模块。然后进行以下更改:
将结果类型更改为使用Maybe
,将结果包装在Just
中,并添加一个产生Nothing
的全能默认案例。
添加额外的参数r
,并将evalExp
的所有递归调用替换为r
。
从中央模块导入包含evalExp
个案例的每个模块。必要时使用合格的导入以避免歧义。定义每个eval函数的列表(它们应该都具有相同的类型),然后将“真实”evalExp
定义为:
expCases = [A.GHC.Num.evalExp, A.GHC.Types.evalExpr {- etc... -} ]
evalExpCases exp = mapMaybe (\eval -> eval evalExp exp) expCases
evalExp exp = case evalExpCases exp of
(r:_) -> -- use the first result
[] -> -- no cases matched
本质上,这是使用Maybe
显式地指示无穷无尽的模式,并用fix
样式构造替换直接递归,其中组合的递归函数被传递给每个(单独的非递归) )一组案例。
这很尴尬,但我不确定是否有更好的方法。可能有一种方法可以使用Template Haskell自动化所有垃圾,但这可能与手动操作一样麻烦。
就个人而言,我可能只是咬紧牙关,把它全部放在一个模块中。