我正在学习 Haskell,目前正在发现数据成员的“访问器”。 假设我有一些虚拟的 2D 顶点信息,一种类型具有一些颜色,另一种类型具有一些纹理坐标 (tc):
data SVertex = VertexC (Float, Float) Int
| VertexTC (Float, Float) (Float, Float)
deriving(Show)
创建记录访问器的一种乏味方法是使用模式编写函数:
position (VertexC (x,y) c ) = (x,y)
position (VertexTC (x,y) c ) = (x,y)
tc (VertexTC _ tc) = tc
color :: SVertex -> Int
color (VertexC _ c) = c
现在,一个积极的功能是我可以为没有“color”或“tc”的访问器添加访问器(“color”和“tc”):
position (VertexC (x,y) c ) = (x,y)
position (VertexTC (x,y) c ) = (x,y) -- no header, here... still works
tc (VertexTC _ tc) = tc
tc (VertexC _ _) = (0,0) -- to returns something even if the field doesn't exist
color :: SVertex -> Int
color (VertexC _ c) = c
color (VertexTC _ _) = 0 -- return something even if field doesn't exist
它允许我为没有任何纹理坐标的顶点指定默认 0 值,或者为没有颜色的顶点指定颜色 0... 一切都好...
现在,我的问题是:我目前正在读到有一种很好的方法可以将访问器名称直接添加到数据声明中。 就我而言,这是我会得到的(使用“prime”来避免名称冲突):
data SVertex' = VertexC' {
position' :: (Float, Float),
color' :: Int
}
| VertexTC' {
position' :: (Float, Float),
tc' :: (Float, Float)
} deriving(Show)
这使我能够达到相同的目标:“position'”、“tc'”和“color”访问器是为我创建的!
但是:我没有找到一种方法来为不存在的字段提供默认访问器。例如,在“VertexC”上请求 tc 时;或在 VertexTC 上请求颜色... 在第一种方法中,我可以实现它。在这种方便的第二种方法中,我担心这是不可能的。 当我尝试添加其他功能模式时,例如
color' (VertexTC' _ _) = 0
编译器告诉我“‘颜色’等的多个声明”。看起来这是因为第二个声明没有在编译器创建的前一个隐式声明之后完成......
你知道解决方法吗?
最佳答案
正如您刚刚发现的,记录与求和类型(即具有多个构造函数的类型)不能很好地混合,因为它们会导致您无法摆脱的令人不快的部分访问器。一种替代方法是仅对实际需要它的字段使用求和类型,而不是使 SVertex
作为一个整体求和类型。这样,您可以获得尽可能多的好的访问器,同时避免部分访问器。
data VertexPaint = VertexC Int | VertexTC (Float, Float)
deriving (Show)
data SVertex = SVertex
{ position :: (Float, Float)
, paintjob :: VertexPaint
} deriving (Show)
如果您想要一个 color
函数,您仍然需要单独定义它,就像您第一次尝试一样。 (这里我将使用 Maybe Int
结果,因为这通常比返回任意默认值更安全。)
color :: SVertex -> Maybe Int
color v = case paintjob v of
VertexC c -> Just c
VertexTC _ -> Nothing
正如 Alec 所建议的,lens库提供了大量的工具来以更方便的方式处理这种情况。无论如何,此答案中定义的类型都可以很好地与lens配合使用。
关于用于不存在记录的 Haskell 访问器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40444501/