Haskell中记录获取器有默认值吗?

4

以下代码 unsurprisingly 抛出了运行时异常:

data Necklace = InvalidNecklace |
    Necklace { necklace_id :: Int, meow :: Int, ... }
necklace_id InvalidNecklace

当应用于 InvalidNecklace 时,是否有自然的方法来定义 necklace_id 的值,使其取代抛出异常?

如果我尝试显而易见的事情,GHC 会报多重声明错误:

necklace_id InvalidNecklace = -1

是否有一些编译指示让GHC将其推断的声明替换为此声明?

我可以通过添加{ necklace_id :: Int }InvalidNecklace声明为记录,但据我所知,我无法保证它始终返回-1,并且通常会变得混乱不堪。我可以简单地定义:

get_necklace_id InvalidNecklace = -1
get_necklace_id x = necklace_id x

但这在一定程度上违背了记录的目的。
我想可以通过编写以下代码来创建一个特殊的invalidNecklace值:
invalidNecklace = Necklace { necklace_id = -1,
     meow = error "meow invalidNecklace accessed", ... }

这种第二种方法有什么缺点吗?我肯定会失去使meow变得严格或未打包的能力,但也许可以维护单独的调试和优化版本。是否有一个局部禁用部分初始化记录警告的编译指示符?


请参阅记录语法访问器的默认值 - Dan Burton
2个回答

8

下面更新:

正如您所发现的,由Necklace声明定义的getter无法进一步定义。 没有可以更改此功能的编译指示。

Haskell中的通常做法是使用这种风格:

get_necklace_id :: Necklace -> Maybe Int
get_necklace_id InvalidNecklace = Nothing
get_necklace_id (Necklace x) = Just x

在C类型语言中,使用“-1”作为神奇返回值的方式很常见,这是因为这些语言具有较简单的类型系统。但请注意,Maybe IntNecklace同构,因此在最简单的情况下它并没有什么用处(除了可以使用处理Maybe的大量通用函数,而这些函数可能不存在于Necklace中)。如果你让Necklace变得更加复杂,那么get_necklace_id就有意义了。

对于较大的项目,可以使用模板Haskell或额外的工具自动创建上述的get_necklace_id

UPDATE: 使用fromJust并不是一个特别好的主意。为了获得“合理的默认值”和“无故障模式”,您可以将get_necklace_id :: Necklace -> Maybe IntData.Maybe.fromMaybe :: a -> Maybe a -> a(一种常见的Maybe处理函数)组合起来使用,像这样:

from_necklace_id :: Int -> Necklace -> Int
from_necklace_id default = fromMaybe default . get_necklace_id

a_necklace_id :: Necklace -> Int
a_necklace_id = from_necklace_id (-1)

a_necklace_id与替换InvalidNecklace为(-1)的函数相同。需要不同默认值的代码可以使用from_necklace_id


我会重新评估 Maybe 是否适用于本地环境,但最初我认为它不合适,因为我最终会到处编写 fromJust,而且 maybe 的常见函数看起来大多不相关。我已经使用另一种方法编辑了问题。 - Jeff Burdges
你可以非常舒适地使用 Maybe,而无需不断地解包和重新封装它们,例如使用 do-Notation、lift... 方法、mfiltersequence 等等,因为 Maybe 是一个单子(monad)以及更多。 - Landei
我正在对各个不同的记录进行各种记录修改,这需要在各个地方使用不同的 liftM (\x -> x { meow=1 }) v 类型表达式,因为 {meow=1} 并不像人们期望的那样创建一个函数。而 do { x <- v, return Just v {meow=1} } 也没有改善情况。最终,我正在列表单子中执行一个大的非确定性计算,而不是在 maybe 单子内执行单遍计算。到处都有 catMaybe 帮助,但建立合理的默认值更有用。 - Jeff Burdges
1
@Jeff: 你也可以看看lenses,它使得使用记录变得更加轻松。例如,在data-lens库中,你可以使用v ^. meowfmap (^. meow) v - Antal Spector-Zabusky
是的,我知道fromMaybe,但从来没有觉得它在这种情况下有吸引力。 - Jeff Burdges

4
为什么不能只使用类型“Necklace”来表示有效的项链,而对于可能无效的项链则使用“Maybe Necklace”?或者,避免使用“Maybe”,可以使用类似以下的命名约定(请注意,我仍然不确定这里应该使用什么好的命名约定):
data Necklace = InvalidNecklace | NecklaceData NecklaceData
data NecklaceData = NecklaceDataRec { necklace_id :: Int, meow :: Int, ... }

本质上,我正在进行一项需要合理默认值但没有故障模式的计算,最好一次性处理它们。 <耸肩> - Jeff Burdges

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接