我应该在 Haskell 中将 make 模块制作多小?
我正在用 Haskell 写一个贪吃蛇游戏。这些是我拥有的一些东西:
Coord
数据类型- A
Line
数据类型 - A
Rect
数据类型 - A
Polygon
类型类,它允许我获取作为一系列线 ([Line]
) 的Rect
。 - 一个
Impassable
类型类,允许我获取作为一系列坐标 ([Coord]
) 的Line
,以便我可以检测其他坐标之间的碰撞无法通行
。 - 一个
Draw
类型类,用于我想要绘制到屏幕上的任何内容 (HSCurses)。 - 最后,我使用 QuickCheck,所以我想为很多这样的事情声明
Arbitrary
实例。
目前我在单独的模块中有很多这样的模块,所以我有很多小模块。我注意到我必须为彼此导入很多,所以我有点想知道这是什么意思。
我对任意实例特别困惑。使用 -Wall
时,当我将这些实例放在一个测试文件中时,我会收到有关孤立实例的警告,我的理解是,我可以通过将这些实例放在与数据类型相同的模块中来避免该警告已定义,但随后我需要为所有这些模块导入 Test.QuickCheck ,这看起来很愚蠢,因为仅在构建测试可执行文件时才需要 QuickCheck。
任何有关 QuickCheck 特定问题的建议以及有关如何/在何处将程序划分为模块的更一般问题的指导都将受到赞赏。
I'm writing a snake game in Haskell. These are some of the things I have:
- A
Coord
data type - A
Line
data type - A
Rect
data type - A
Polygon
type class, which allows me to get aRect
as a series of lines ([Line]
). - An
Impassable
type class that allows me to get aLine
as a series of Coords ([Coord]
) so that I can detect collisions between otherImpassable
s. - A
Draw
type class for anything that I want to draw to the screen (HSCurses). - Finally I'm using QuickCheck so I want to declare
Arbitrary
instances for a lot of these things.
Currently I have a lot of these in separate modules so I have lots of small modules. I've noticed that I have to import a lot of them for each other so I'm kind of wondering what the point was.
I'm particularly confused about Arbitrary
instances. When using -Wall
I get warnings about orphaned instances when I but those instances together in one test file, my understanding is that I can avoid that warning by putting those instances in the same module as where the data type is defined but then I'll need to import Test.QuickCheck
for all those modules which seems silly because QuickCheck should only be required when building the test executable.
Any advice on the specific problem with QuickCheck would be appreciated as would guidance on the more general problem of how/where programs should be divided into modules.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
你可以鱼与熊掌兼得。您可以重新导出模块。
每当我有一个完整的抽象时,即当数据类型的含义与其实现不同时,我通常会使用模块。对您的代码知之甚少,我可能会将
Polygon
和Impassable
组合在一起,也许会创建一个Collision
数据类型来表示它们返回的内容。但是,Coord、Line 和 Rect 似乎是很好的抽象,它们可能值得拥有自己的模块。You can have your cake and eat it too. You can re-export modules.
I usually use a module whenever I have a complete abstraction -- i.e. when a data type's meaning differs from its implementation. Knowing little about your code, I would probably group
Polygon
andImpassable
together, perhaps making aCollision
data type to represent what they return. ButCoord
,Line
, andRect
seem like good abstractions and they probably deserve their own modules.出于测试目的,我对
Arbitrary
实例使用单独的模块。尽管我通常会避免孤立实例,但这些模块仅在构建测试可执行文件时构建,因此我不介意孤立实例或它不是-Wall
干净的。您还可以使用 -fno-warn-orphans 来禁用此警告消息。For testing purposes, I use separate modules for the
Arbitrary
instances. Although I generally avoid orphan instances, these modules only get built when building the test executable so I don't mind the orphans or that it's not-Wall
clean. You can also use-fno-warn-orphans
to disable just this warning message.我通常更强调由它公开的函数定义的模块接口,而不是它公开的数据类型。您的某些类型是否共享一组通用的功能?然后我会把它们放在同一个模块中。
但我的做法可能不是最好的,因为我通常编写小程序。我建议查看 Hackage 中的一些代码,看看包维护者做了什么。
如果有一种方法可以根据社区评级或下载次数对软件包进行排序,那么这将是一个很好的起点。 (我以为有,但现在我寻找它,我找不到它。) 如果失败,请查看您已经使用的软件包。
I generally put more emphasis on the module interface as defined by the functions it exposes rather than the data types it exposes. Do some of your types share a common set of functions? Then I would put them in the same module.
But my practise is probably not the best since I usually write small programs. I would advise looking at some code from Hackage to see what package maintainers do.
If there were a way to sort packages by community rating or number of downloads, that would be a good place to start. (I thought there was, but now that I look for it, I can't find it.) Failing that, look at packages that you already use.
QuickCheck 的一种解决方案是在测试时使用 C 预处理器有选择地启用任意实例。您将任意实例直接放入主模块中,但用预处理器宏包装它们,然后将“测试”标志放入 Cabal 文件中。
One solution with QuickCheck is to use the C preprocessor to selectively enable the Arbitrary instances when you are testing. You put the Arbitrary instances straight into your main modules but wrap them with preprocessor macros, then put a "test" flag into your Cabal file.