2011-12-24 59 views
9

我有定义了如下记录:Haskell记录中的“依赖”字段?

data Option = Option { 
    a :: Maybe String, 
    b :: Either String Int 
} deriving (Show) 

反正对我来说,强制执行,当是Nothing,B必须是Left当a是Just,B必须是Right?也许幻影类型,或其他?或者我必须将整个东西都包裹在一个Either中,并使其成为Either String (String, Int)

+0

我不认为Haskell 2010有什么可以让你在以前的解决方案。我会建议去'Either String(String,Int)',或者一个同构类型,比如'data Option = OptionA String | OptionB String Int',其中构造函数具有明智的名称。 – 2011-12-24 17:15:48

+1

为了最好地回答这个问题,知道附加在a和b上的*意思很重要。为什么有时候你没有'a'(例如它是'Nothing')?为什么可以将'b'表示为'String' *或* Int'?你为什么要强制执行你所谈论的限制?这给了我最初的印象:“b”没有很好的定义。 – 2011-12-24 20:08:06

回答

17

你应该只使用两个构造两个可能的形状:

data Option = NoA String | WithA String Int 

当然,你应该给他们更好的名字,根据他们代表什么。幻像类型在这里肯定是矫枉过正,我建议避免Either - LeftRight不是很自我记录的构造函数名称。

如果是有意义的解释B场的两者或者分支机构表示相同的数据,那么你应该定义一个反映这种解释的功能:

b :: Option -> MeaningOfB 
b (NoA s) = ... 
b (WithA t n) = ... 

如果您有保持不变没有字段无论选择什么,你都应该用它们全部创建一个新的数据类型,并将它包含在两个构造函数中。如果您为每个构造函数创建一条记录,则可以在每个构造函数中为公共字段指定相同的名称,以便您可以从任何Option值中提取该值,而无需在其上进行模式匹配。

基本上,想想它意味着字符串不存在:它改变了关于其他领域,什么保持不变?无论相应的施工人员有何变化,不管保持不变,都应该分解成它自己的类型。 (这是一个很好的设计原则!)

如果你来自OOP背景,你可以用构图而不是继承的方式来思考这个问题 - 但是尽量不要把这个比喻说得太过分。