为什么我不能将类型 Int 与类型 a 匹配

Why can't I match type Int with type a

Haskell 菜鸟在这里。

我在这里尝试做的事情的一个过于简单的例子:

test :: Int -> a
test i = i -- Couldn't match expected type ‘a’ with actual type ‘Int’. ‘a’ is a rigid type variable bound by ...

我不太明白为什么这行不通。我的意思是,Int 肯定包含在 a.

类型的东西中

我真正想要实现的是:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}

data EnumType = Enum1 | Enum2 | Enum3

data MyType (a :: EnumType) where
  Type1 :: Int -> MyType 'Enum1
  Type2 :: String -> MyType 'Enum2
  Type3 :: Bool -> MyType 'Enum3

myFunc :: EnumType -> MyType 'Enum1 -> MyType any
myFunc Enum1 t = t -- Can't match type `any` with `Enum1`. any is a rigid type variable bound by ...
myFunc Enum2 _ = Type2 "hi"
myFunc Enum3 _ = Type3 True

这是怎么回事?有没有办法解决这个问题,还是您不能这样做?

Is there a way to work around this or is it just something you can't do?

恐怕这只是“你做不到的事情”。原因很简单。

当你写一个类型签名时(以你的第一个更简单的例子为例)

test :: Int -> a

或者,写成更直白的扩展形式

test :: forall a. Int -> a

你的意思是字面上的意思,“对于所有 a”,这个函数可以接受一个 Int 和 return 一个 a 类型的值。这很重要,因为调用代码必须相信这个类型签名,因此能够做这样的事情(这不是现实的代码,但想象一下你将 test 2 的结果提供给需要的函数的情况Char 或其他类型之一):

test 2 :: Char
test 2 :: [Int]
test 2 :: (Double, [Char])

等等。很明显,您的函数无法与这些示例中的任何一个一起使用 - 但它 必须 能够与其中的 any 一起使用,如果您给它这个类型签名。很简单,您的代码不符合该类型签名。 (而且也不可能,除非你通过 test x = undefined 来“作弊”。)

不过这应该不是问题 - 编译器只是在保护您免于出错,因为我相信您意识到您的代码无法满足此类型签名。以你的“真实”为例:

myFunc :: EnumType -> MyType Enum1 -> MyType any

虽然这会产生编译错误,但函数中的代码可能是正确的,问题出在类型签名上。如果将其替换为

myFunc :: EnumType -> MyType Enum1 -> MyType Enum1

然后它将编译(除非有任何进一步的错误,我没有检查过),并且大概会做你想做的事。看起来您实际上并不希望能够调用 myFunc 并让它生成一个 MyType Int。 (如果你有机会这样做,我建议你问一个单独的问题,在这里详细说明你实际需要什么。)

对于你要写的GADT函数,标准的技术是使用单例。问题是 EnumType 类型的值是值级别的东西,但你想通知类型系统一些事情。因此,您需要一种方法将 types of kind EnumTypevalues of type EnumType(它本身有种类 Type)。这是不可能的,所以我们作弊:我们将种类 EnumType 的类型 x 与新类型 SEnumType x 的值连接起来,这样该值唯一确定 x。外观如下:

data SEnumType a where
    SEnum1 :: SEnumType Enum1
    SEnum2 :: SEnumType Enum2
    SEnum3 :: SEnumType Enum3

myFunc :: SEnumType a -> MyType Enum1 -> MyType a
myFunc SEnum1 t = t
myFunc SEnum2 _ = Type2 "hi"
myFunc SEnum3 _ = Type3 True

现在 return 类型 MyType a 中的 a 不是凭空捏造的;它被限制为等于来自 SEnumType 的传入 a,并且 SEnumType 所在的模式匹配让您观察 a 是否为 Enum1Enum2Enum3.

如前所述,您的签名表示通用类型

myFunc :: ∀ a . EnumType -> MyType 'Enum1 -> MyType a

而你实际上想表达的是存在类型

myFunc :: EnumType -> MyType 'Enum1 -> (∃ a . MyType a)

Haskell 没有类似的功能,但它确实有一些方法可以实现基本相同的功能。

  1. GADT 和 ExistentialTypes 扩展都允许表达存在性,但您需要为它们定义一个单独的类型。

    data MyDynType where
      MyDynWrap :: MyType a -> MyDynType
    
    myFunc :: EnumType -> MyType 'Enum1 -> MyDynType
    myFunc Enum1 t = MyDynWrap t
    myFunc Enum2 _ = MyDynWrap $ Type2 "hi"
    myFunc Enum3 _ = MyDynWrap $ Type3 True
    
  2. 也许您甚至不需要单独的类型,但可以简单地将 MyType 修改为“动态”。

    data MyType = Type1 Int | Type2 String | Type3 Bool
    
    myFunc :: EnumType -> MyType -> MyType
    myFunc Enum1 (Type1 i) = Type1 i
    myFunc Enum2 _ = Type2 "hi"
    myFunc Enum3 _ = Type3 True
    
  3. existentials 可以在现场匿名模拟,方法是打开一层连续传递样式,然后通过 RankNTypes 扩展使用双通用量词。

    {-# LANGUAGE RankNTypes #-}
    
    data MyType (a :: EnumType) where ... -- as original
    
    myFunc :: EnumType -> MyType 'Enum1 -> (∀ a . MyType a -> r) -> r
    myFunc Enum1 t q = q t
    myFunc Enum2 _ q = q (Type2 "hi")
    myFunc Enum3 _ q = q (Type3 True)
    

你要写的GADT函数,标准的技术是使用单例。问题是 EnumType 类型的值是 website...