有没有一种特性(模式同义词?)可以把一个数据类型变成用于构造另一个数据类型的语法糖?

后端开发 2026-07-12

假设我在我的库里定义了这个数据类型

data Foo = Foo (String, String, String, String)

并且我暴露了一堆可供使用的东西,但客户端实际上只需要构造一个 Foo 并将其传递给我定义的API。其他的什么都不用。

现在说,为了给客户端提供更多的灵活性,我想出了一个更好的数据类型

data Bar = Bar (Bool -> Int -> Bool -> String)

显然我不想搞坏现有的调用点,我可以通过把旧的 Foo 与新的 Bar 一起保留来确保这一点,客户端在寻找更多灵活性时应该能发现 Bar

然而,由于一些无关的原因,同时拥有 FooBar 有点烦人(例如,因为我必须在多个地方写它们)。

所以,最近接触到 pattern synonyms,我在想是否可以把 Foo 转化为创建 Bar 的零成本语法糖。¹

不知为何,感觉我需要 Pattern synonyms 功能的“向后”部分,意思是

  1. pattern synonyms的三种形式总是提供对数据类型进行模式匹配的另一种方式,
  2. 但只有其中两种给出构造一个值的另一种方式,

但在我的用例中,我并不需要第一点,但确实需要第二点。

确实,我希望来自假设的语法糖机器能够把任何出现的

Foo abcd

变成

let (a, b, c, d) = abcd
in Bar (\b1 i b2 -> "function of a, b, c, d, b1, i, b2 hardcoded in the library")

但这在上面的第一点中是做不到的。


¹ 当我写这个问题时,我在想一种做法是把 Foo 构造器从 Foo 类型复制粘贴到 Bar 类型,并删除 Foo 类型。可以说这不是我在寻找的解决方案。我猜这不会是零成本的,因为我得把所有接受 Bar 的函数改为通过对 BarFoo 构造的值进行模式匹配来分支,对吗?

解决方案

现有技术下,最好的做法可能就是把模式匹配转换为运行时错误,而不是编译时错误。就像这样:

data Bar = Bar (Bool -> Int -> Bool -> String)
pattern Foo :: HasCallStack => String -> Bar
pattern Foo s <- (error "Foo is for construction only" -> s)
    where Foo s = Bar (\_ _ _ -> s)

在ghci中试试:

> let Foo x = Bar mempty in length x
*** Exception: Foo is for construction only
CallStack (from HasCallStack):
  error, called at test.hs:9:19 in main:Main
  Foo, called at <interactive>:1:5 in interactive:Ghci1
> let Bar f = Foo "x" in f True 3 False
"x"

你也可能需要 type Foo = Bar,以防你的现有调用方在类型签名中提到 Foo

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章