注意:如果这个问题有些奇怪,那是因为我最近才接触到Haskell,并且仍在适应功能性思维。
考虑类似Maybe
的数据类型:
data MyOwnMaybe a = MyOwnNothing | MyOwnJust a
每个使用我的数据类型的人都会编写像
这样的函数maybeToList :: MyOwnMaybe a -> [a]
maybeToList MyOwnNothing = []
maybeToList (MyOwnJust x) = [x]
现在,假设稍后我希望扩展此数据类型
data MyOwnMaybe a = MyOwnNothing | MyOwnJust a | SuperpositionOfNothingAndJust a
如何确保每个人的功能在编译时都会中断?
当然,有可能我不会“获得”代数数据类型,也许我根本不应该这样做,但考虑数据类型Action
data Action = Reset | Send | Remove
似乎添加Action
之类的额外Add
并不是那么罕见(我不想冒险让所有这些功能无法处理我的新Action
})
答案 0 :(得分:3)
您似乎知道GHC可以通过-W
标志或明确地使用-fwarn-incomplete-patterns
警告功能上的非详尽模式匹配。
关于为什么这些警告不是在这个SO问题上自动编译错误的问题,这是一个很好的讨论:
In Haskell, why non-exhaustive patterns are not compile-time errors?
另外,考虑这种情况,你有一个带有大量构造函数的ADT:
data Alphabet = A | B | C | ... | X | Y | Z
isVowel :: Alphabet -> Bool
isVowel A = True
isVowel E = True
isVowel I = True
isVowel O = True
isVowel U = True
isVowel _ = False
使用默认情况是为了避免必须写出其他21个案例。
现在,如果你向Alphabet
添加一个额外的构造函数,那么isVowel
应该被标记为"不完整"?
答案 1 :(得分:3)
但这与语言无关;在任何语言中,你有时必须打破界面。没有办法解决它。
现在,好消息:在你必须这么做之前,你实际上可以花很长时间。
您只需仔细考虑从模块中导出的内容。如果您导出高级函数而不是导出它的内部工作,那么很有可能您可以使用新数据类型重写这些函数,一切都会顺利进行。
特别是在导出数据构造函数时要非常小心。在这种情况下,您不必只导出创建数据的函数;你也出口模式匹配的可能性;而且这不是让你很紧张的事情。
因此,在您的示例中,如果您编写类似
的函数myOwnNothing :: MyOwnMaybe a
myOwnJust :: a -> MyOwnMaybe a
和
fromMyOwnMaybe :: MyOwnMaybe a -> b -> (a -> b) -> b
fromMyOwnMaybe MyOwnNothing b _ = b
fromMyOwnMaybe (MyOwnJust a) _ f = f a
那么可以合理地假设您可以为更新的MyOwnMaybe
数据类型重新实现它;所以,只需导出这些函数和数据类型本身,但不要导出构造函数。
您可以从导出构造函数中受益的唯一情况是,您绝对确定您的数据类型不会发生变化。例如,Bool
始终只有两个(完全定义的)值:True
和False
,它不会被某些FileNotFound
或任何内容扩展(尽管Edward Kmett可能不同意)。同上Maybe
或[]
。
但这个想法更为笼统:保持尽可能高的水平。
答案 2 :(得分:1)
许多模块做的一件事是不要导出他们的构造函数。相反,它们导出可以使用的功能(“智能构造器”)。如果您稍后更改ADT,则必须在模块中修复您的功能,但其他人的代码不会被破坏。