【问题标题】:Union in GADT type variableGADT 类型变量中的联合
【发布时间】:2018-04-28 09:58:19
【问题描述】:

我有一个代表相同想法的 GADT 构造函数,但我需要其中的两个,因为类型根据上下文是灵活的。看这个人为的例子:

data A
data B
data C

data Thing a where
  AFoo :: String -> Thing A
  Bar  :: Float -> Thing A
  BFoo :: String -> Thing B
  Baz  :: Int -> Thing B
  Bah  :: Char -> Thing C

AFooBFoo 代表相同的底层数据概念:一些带有字符串的数据。但在这种情况下,它不仅如此。理想情况下,AFooBFoo 将合并到Foo 中,因为它们代表完全相同的东西,但我需要一个像Thing A 这样的类型和一个像Thing B 这样的类型。如前所述,可以使用Foo 的某些上下文需要Thing A,而有些则需要Thing B,因此为了满足类型系统,每个上下文都需要存在一个构造函数。

如果需要,我当然可以通过切换构造函数来编写一个“转换”为所需类型的函数:

fooAsThingA :: Thing a -> Maybe (Thing A)
fooAsThingA t@(AFoo _) = Just t
fooAsThingA (BFoo s) = Just $ AFoo s
fooAsThingA _ = Nothing

fooAsThingB 也是如此)

但这很丑陋,因为它需要我必须传播的Maybe,因为并非所有Thing Bs 都可以变成Thing As(实际上,只有BFoo 可以)。

理想情况下,我想写:

data A
data B
data C

data Thing a where
  Foo :: String -> Thing ??
  Bar :: Float -> Thing A
  Baz :: Int -> Thing B
  Bah :: Char -> Thing C

但我不清楚我会用什么代替??。大概它是表示AB 联合的某种方式(也许这需要更改此 GADT 的其他构造函数的类型,但这很好)。

为了清楚起见,如果上述内容有效,我希望给出以下功能:

processThingA :: Thing A -> String
processThingB :: Thing B -> Int
processThingC :: Thing C -> Float

然后,我就可以做以下所有事情了:

processThingA $ Foo "Hello"
processThingB $ Foo "World"

processThingA $ Bar 3.14
processThingB $ Baz 42

processThingC $ Bah '\n'

tl;dr 在第一个 sn-p 中,是否可以将 AFooBFoo 合并到一个类型为 Thing AThing BFoo 中?

编辑:注意:除了AB 之外,还有其他EmptyDataDecls 用于Thing a。我添加了C 作为示例。

【问题讨论】:

  • data Thing a where Foo :: String -> Thing a ... 怎么样?或者您可以使用DataKinds 对其进行一些限制:data ThingType = A | B; data Thing (a :: ThingType) where Foo :: String -> Thing a ...。这些适合您的用例吗?
  • @DavidYoung 哦,这是一个有趣的解决方案。我没怎么玩过DataKinds。我确实有其他类型(更新问题以添加Thing C),我不想加入这个“联盟”,所以我认为第一个不会起作用。但是如果我做了data AOrB = A | B; data C 是否有办法使用DataKinds 来限制Foo 构造函数的类型?
  • 您可以使用Foo :: String -> AorB t -> Thing t,其中AorB t 是合适的GADT,仅由JustA :: AorB AJust B :: AorB B 填充。这不是很方便,并且有一些内存开销,但它可以很好地工作。原则上,您甚至可以使用类型类来保持该见证“隐式”。开销仍然存在,但不必显式传递。
  • @chi 谢谢!那么例如,我必须做Bar :: Float -> Thing JustA?对于Bah,我会保留data C 并让它成为Bah :: Char -> Thing C?我并不太担心内存开销,但我有点恼火,因为这需要对一个额外的无用值进行模式匹配。您能否详细说明如何使用类型类来完成此操作?这对我来说不是很明显。
  • @chi 另外,在尝试过这个之后,看起来这只是将问题推向了AorB 值。如何实例化FooFoo "abc" JustA 只能在 Thing (AorB A) 上下文中使用。似乎我仍然需要一个类似“运行时转换”的东西来将 Foo "abc" JustA 更改为 Foo "abc" JustB 以便能够将其传递给期望 Thing (AorB B) 的函数(这有前面提到的问题,即过于宽泛类型为Thing a -> Thing (AorB A),需要Maybe)。有没有办法避免这种情况?

标签: haskell gadt


【解决方案1】:

一个可能的解决方案如下,即使我认为它不优雅。

我们首先定义一个 GADT 和一个类型类作为“A 或 B”:

{-# LANGUAGE DataKinds, KindSignatures, GADTs, EmptyCase,
             ScopedTypeVariables #-}
{-# OPTIONS -Wall #-}

data Sort = A | B | C | D

data AorB (s :: Sort) where
  IsA :: AorB 'A
  IsB :: AorB 'B

class IsAorB (s :: Sort) where
  aorb :: AorB s

instance IsAorB 'A where aorb = IsA
instance IsAorB 'B where aorb = IsB

然后我们在我们的类型中利用我们的类型类。这将导致Foo 需要更多空间,因为它需要在运行时存储类型类字典。这是少量的开销,但仍然很不幸。另一方面,这也可以在运行时辨别Foo中使用的s实际上是A还是B

data Thing (s :: Sort) where
  Foo :: IsAorB s => String -> Thing s 
  Bar :: Float -> Thing 'A
  Baz :: Int -> Thing 'B
  Bah :: Char -> Thing 'C

一些测试:

processThingA :: Thing 'A -> String
processThingA (Foo x) = x
processThingA (Bar x) = show x

processThingB :: Thing 'B -> Int
processThingB (Foo _) = 0
processThingB (Baz i) = i

一个主要的缺点是我们需要让详尽检查器相信我们的模式匹配是可以的。

-- This unfortunately will give a spurious warning
processThingC :: Thing 'C -> Float
processThingC (Bah _) = 42.2
-- To silence the warning we need to prove that this is indeed impossible
processThingC (Foo _) = case aorb :: AorB 'C of {}

processThingAorB :: forall s. IsAorB s => Thing s -> String
processThingAorB (Foo x) = x
processThingAorB (Bar x) = "Bar " ++ show x
processThingAorB (Baz x) = "Baz " ++ show x
-- Again, to silence the warnings
processThingAorB (Bah _) = case aorb :: AorB 'C of {}

test :: ()
test = ()
  where
  _ = processThingA $ Foo "Hello"
  _ = processThingB $ Foo "World"
  _ = processThingA $ Bar 3.14
  _ = processThingB $ Baz 42
  _ = processThingC $ Bah '\n'

这种技术不能很好地扩展。对于Thing 的任何构造函数,我们需要一个自定义的 GADT 和类型类,它们可以在某个“联合”中生成任何标记。这仍然可以。为了检查详尽无遗,我们需要利用所有这些类。

我认为这应该需要线性数量的样板,但它仍然很重要。

也许使用单例更简单,在一般情况下,将 s 的值存储在构造函数 Foo 中,并避免所有自定义 GADT 和类型类。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-26
    • 2018-09-28
    • 2021-02-02
    • 2012-02-07
    • 1970-01-01
    • 2016-09-07
    相关资源
    最近更新 更多