如何保持Haskell的强类型灵活性?

7
我一直在使用Haskell编写逐渐增长的代码库。我的问题是,我根据GHCI告诉我的函数类型签名添加到了函数中。
现在问题是,随着代码库的不断增长,只要我改变一个东西,我的代码就会遍布各处而我需要耗费大量时间来跟踪所有问题。
在GHCi中加载模块时是否推导出的类型过于具体?我应该如何决定在我的签名中使用哪些类型或类型类以利用强类型的优势并保持一定的灵活性(即不花费一小时传播微小的更改)?

4
这似乎有点奇怪。你到底做了什么使得所有的代码都出问题了?你不断改变类型吗?通常情况下,人们会在一段时间内(甚至是在开始时)固定一个类型设计,并且大部分工作将集中在其余的代码上(在写好的 Haskell 中应该非常独立)。同时请注意,如果您正在为类型添加构造函数,则处理该类型的代码出现问题并且编译器会指出潜在问题也并不奇怪,虽然如果它“不应该”破坏代码,泛型可以帮助解决问题... - Jedai
5
也许你需要创建一些类型的同义词或定义类型,这样你的更改就可以局限在一个地方了? - augustss
2
你能举一些例子说明你想要实现什么,以及你期望它应该如何运作吗? - ondra
由于我的代码规模/复杂性很大,很难举例。也许这是一个不好的迹象 :/。似乎去除单态限制可以解决问题(ghci提供更通用的typedefs)。像将类型从RealFrac更改为Real这样有意义(对我来说)的事情有很多后果,一旦我跟踪到它们,总会有更多的问题,我会陷入到一个版本化的DLL地狱中。更改一个,另一个会出错,更改另一个... - Toymakerii
1个回答

7
现在问题是我的代码库越来越大,只要我改变一件事情,我的代码就会到处出错,我不得不花费大量时间来追踪所有的问题。
在Yesod(一个Haskell web框架)中,这实际上被宣传为一种功能。假设我已经指定了以下路由规范:
/blog/#String         BlogR   GET

我决定将其更改为

/blog/#Date/#String   BlogR   GET

一旦我对路由进行了这个更改,编译器就会告诉我到处都破坏了我的代码。我将被迫更新getBlogR函数——改变其输入类型,使其也接受Date。我还将被迫更新在我的模板中使用类型安全URL的任何地方,这看起来像@{BlogR (slug p)}——>@{BlogR (date p) (slug p)}
这被认为是一件好事,因为类型检查器正在帮助您找到由您所做的更改引入的问题。
现在,关于ghci。
ghci> let shew = show
ghci> :t shew
shew :: () -> String
ghci> :t show
show :: Show a => a -> String

有时候 ghci 选择的默认值可能会很烦人。不过您可以缓解这种情况。
ghci> :set -XNoMonomorphismRestriction
ghci> let shew = show
ghci> :t shew
shew :: Show a => a -> String

虽然使用ghci发现一个函数的类型对初学者来说是非常好的,但我不建议依赖于ghci。学习什么是类型签名,以及如何自己发现它们。事实上,开始编写一个函数时,先编写你想要的类型签名。花费一点时间学习这个技能是非常值得的,当您能够充分利用Haskell的类型系统时,它可以成为编程的很大助益。


1
说了这么多,你问题的真正答案可能就是augustss建议的:使用类型同义词和类型定义来清理你的代码。 - Dan Burton
太棒了!我的问题是类型系统太复杂了,对于初学者来说,我只能猜测签名,最后只好向ghci求助。(我目前的问题是与数字类型类有关)。 - Toymakerii
有没有办法告诉GHCI或一个linting程序,将我的硬编码定义与可能更好、更通用的版本进行比较? - Toymakerii
1
我不知道有任何工具可以做到这一点。通常只需要1)注释类型签名2)查看ghci的输出。您可能需要为文件禁用单态限制(:set仅适用于在ghci中定义的函数),方法是在文件顶部添加此咒语 {-# LANGUAGE NoMonomorphismRestriction #-} - Dan Burton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接