扩展代数数据类型

时间:2015-08-30 17:29:31

标签: haskell algebraic-data-types

注意:如果这个问题有些奇怪,那是因为我最近才接触到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 })

3 个答案:

答案 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始终只有两个(完全定义的)值:TrueFalse,它不会被某些FileNotFound或任何内容扩展(尽管Edward Kmett可能不同意)。同上Maybe[]

但这个想法更为笼统:保持尽可能高的水平。

答案 2 :(得分:1)

许多模块做的一件事是不要导出他们的构造函数。相反,它们导出可以使用的功能(“智能构造器”)。如果您稍后更改ADT,则必须在模块中修复您的功能,但其他人的代码不会被破坏。