【问题标题】:Haskell accessors for non-existing records用于不存在记录的 Haskell 访问器
【发布时间】:2016-11-07 07:33:38
【问题描述】:

我正在学习 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

编译器告诉我“‘color’等的多个声明”。似乎是因为第二个声明不是在编译器创建的前一个隐式声明之后完成的......

你知道解决方法吗?

【问题讨论】:

  • 我不相信这是可能的。 Haskell 记录通常是一个痛点(尤其是它们是部分的,这就是你所得到的)。如果您对记录感兴趣,您可能最终会在某个时候查看lens(使用Prism 抽象处理这种偏颇性)。
  • 这是不可能的,但它也是非惯用 Haskell 的一个指标。当您使用SVertex 实例来处理这些默认值时,请考虑更多地使用模式匹配,而不是制定全局规则(后一种方法感觉非常Java-esque)。
  • 我没有意识到在 Haskell 中访问“记录”有这么大的缺陷。感谢您确认我应该如何意识到这一点。似乎有些人正在尝试在此范围内进行改进:nikita-volkov.github.io/record

标签: haskell accessor


【解决方案1】:

正如您刚刚发现的,记录不能很好地与 sum 类型(即具有多个构造函数的类型)混合,因为它们会导致您无法摆脱的不愉快的部分访问器。一种替代方法是仅将 sum 类型用于实际需要它的字段,而不是使 SVertex 作为一个整体成为 sum 类型。这样,您可以获得尽可能多的好访问器,同时避免部分访问器。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多