我如何将 TypeApplications 与类型类方法一起使用,为什么 GHCi 会推断出我无法使用的类型?
How do I use TypeApplications with typeclass methods, and why does GHCi infer a type that I can't use?
总结
我有一个类型类,我想为其编写一些 'generic terms'。我有两个问题:
- 使用
:t
向 GHCi 询问通用术语的类型是可行的,但使用该推断类型会失败 - 为什么?
- 如何将
TypeApplications
与类型类的方法一起使用?
我正在使用 GHC 8.8.4。对于这两个问题,我有以下示例 Main.hs
,其中包含类型类 F
和类型 Empty
,它是 F
.
的一个实例
{-# LANGUAGE NoStarIsType #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Main where
import GHC.Types (Type)
class F (f :: k -> Type) where
type Plus f (a :: k) (b :: k) :: k
zero :: f a
plus :: f a -> f b -> f (Plus f a b)
data Empty (a :: Type) = Empty
instance F Empty where
type Plus Empty a b = (a, b)
zero = Empty
plus _ _ = Empty
1。推断类型不起作用?
我想构造一个类型类 F
的通用术语。例如,plus zero zero
。
当我询问 GHCi 这个词的类型时,它给出了我所期望的:
*Main> :t plus zero zero
plus zero zero :: F f => f (Plus f a b)
令人惊讶的是,如果我尝试 分配 这个词,我会得到一个错误。也就是说,如果我将以下内容添加到 Main.hs
:
-- This doesn't work.
plusZero :: F f => f (Plus f a b)
plusZero = plus zero zero
在 GHCi 中重新加载文件报错:
• Couldn't match type ‘Plus f a0 b0’ with ‘Plus f a b’
Expected type: f (Plus f a b)
Actual type: f (Plus f a0 b0)
NB: ‘Plus’ is a non-injective type family
The type variables ‘a0’, ‘b0’ are ambiguous
• In the expression: plus zero zero
In an equation for ‘plusZero’: plusZero = plus zero zero
我的第一个问题是:为什么 GHCi 似乎可以推断类型,但当我明确注释术语时却拒绝它?
2。使用 TypeApplications
而不是注释
我可以简单地通过注释 zero
项的类型来解决第一个问题:
-- This works
plusZero1 :: forall f a b . F f => f (Plus f a b)
plusZero1 = plus (zero :: f a) (zero :: f b)
但是,当项变大时,这有点笨拙。我想做的是使用 TypeApplications
。我试过这个:
-- This doesn't work
plusZero2 :: forall f a b . F f => f (Plus f a b)
plusZero2 = plus @f @a @b zero zero
但是 GHCi 抱怨:
• Expecting one more argument to ‘f’
Expected a type, but ‘f’ has kind ‘k -> *’
• In the type ‘f’
In the expression: plus @f @a @b zero zero
In an equation for ‘plusZero2’: plusZero2 = plus @f @a @b zero zero
• Relevant bindings include
plusZero2 :: f (Plus f a b) (bound at Main.hs:36:1)
奇怪的是,如果我首先按如下方式定义附加函数 plus'
和 zero'
,一切都会按预期运行:
zero' :: forall f a . F f => f a
zero' = zero
plus' :: forall f a b . F f => f a -> f b -> f (Plus f a b)
plus' = plus
-- This works fine
plusZero3 :: forall f a b . F f => f (Plus f a b)
plusZero3 = plus' @f @a @b zero' zero'
看来我还没有理解 TypeApplications
是如何与类型类方法一起工作的。
如何在不定义附加函数 plus'
和 zero'
的情况下使用具有 plus
和 zero
的类型应用程序?
- Inferred Types don't work?
在你的例子中GHC确实可以推断类型,但它不能接受你的签名。这可能看起来有悖常理,但如果您考虑一般情况,它确实有道理。
Plus f a b
是一个非单射类型族。对于 GHC 在类型检查时知道的所有内容,它可以定义为 Plus f a b = a
用于所有 f
、a
和 b
.
假设我们已经定义了一个术语(为了清楚起见,我添加了 forall
s)
foo :: forall f a b. F f => f (Plus f a b)
然后我们写
bar :: forall f a b. F f => f (Plus f a b)
bar = foo
这不应该进行类型检查 (!),因为它本质上是模棱两可的。作为人类的程序员可能希望编译器推断这些类型:
bar :: forall f a b. F f => f (Plus f a b)
bar = foo @f @a @b
但是,可能还有其他正确的推断类型!事实上,如果 Plus
是如上所述定义的,这也将键入 check:
bar :: forall f a b. F f => f (Plus f a b)
bar = foo @f @a @String
使用它,foo
将生成 f (Plus f a String)
,它与 f (Plus f a b)
相同,因此所有内容都会进行类型检查。由于程序员可能打算使用 @b
以外的其他东西,我们在这里停止报告类型错误的歧义。
从技术上讲,推理期间发生的事情是这样的:对 poltmorphic foo
的调用链接到新的未知类型变量:
bar :: forall f a b. F f => f (Plus f a b)
bar = foo @xf @xa @xb
然后,统一发生:foo @xf @xa @xb
的类型是 xf (Plus xf xa xb)
并与提供的签名统一以查找未知数:
xf (Plus xf xa xb) ~ f (Plus f a b)
我们应用统一算法:
xf ~ f
Plus xf xa xb ~ Plus f a b
所以我们找到未知 xf
的类型,代入我们得到:
xf ~ f
Plus f xa xb ~ Plus f a b
然而,我们到此为止。我们无法推断 xa ~ a
和 xb ~ b
,因为类型族不是单射的。
- Using TypeApplications instead of annotations
问题是有一个隐藏的类型 @k
参数,因为它出现在 class 中。使用 :t +v
显示所有 forall
的真实类型:
> :t +v plus
plus
:: forall k (f :: k -> *) (a :: k) (b :: k).
F f =>
f a -> f b -> f (Plus f a b)
传递 @k
也有效:
plusZero2 :: forall k (f :: k -> Type) a b . F f => f (Plus f a b)
plusZero2 = plus @k @f @a @b zero zero
或者,让编译器推断出 @k
:
plusZero2 :: forall f a b . F f => f (Plus f a b)
plusZero2 = plus @_ @f @a @b zero zero
总结
我有一个类型类,我想为其编写一些 'generic terms'。我有两个问题:
- 使用
:t
向 GHCi 询问通用术语的类型是可行的,但使用该推断类型会失败 - 为什么? - 如何将
TypeApplications
与类型类的方法一起使用?
我正在使用 GHC 8.8.4。对于这两个问题,我有以下示例 Main.hs
,其中包含类型类 F
和类型 Empty
,它是 F
.
{-# LANGUAGE NoStarIsType #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Main where
import GHC.Types (Type)
class F (f :: k -> Type) where
type Plus f (a :: k) (b :: k) :: k
zero :: f a
plus :: f a -> f b -> f (Plus f a b)
data Empty (a :: Type) = Empty
instance F Empty where
type Plus Empty a b = (a, b)
zero = Empty
plus _ _ = Empty
1。推断类型不起作用?
我想构造一个类型类 F
的通用术语。例如,plus zero zero
。
当我询问 GHCi 这个词的类型时,它给出了我所期望的:
*Main> :t plus zero zero
plus zero zero :: F f => f (Plus f a b)
令人惊讶的是,如果我尝试 分配 这个词,我会得到一个错误。也就是说,如果我将以下内容添加到 Main.hs
:
-- This doesn't work.
plusZero :: F f => f (Plus f a b)
plusZero = plus zero zero
在 GHCi 中重新加载文件报错:
• Couldn't match type ‘Plus f a0 b0’ with ‘Plus f a b’
Expected type: f (Plus f a b)
Actual type: f (Plus f a0 b0)
NB: ‘Plus’ is a non-injective type family
The type variables ‘a0’, ‘b0’ are ambiguous
• In the expression: plus zero zero
In an equation for ‘plusZero’: plusZero = plus zero zero
我的第一个问题是:为什么 GHCi 似乎可以推断类型,但当我明确注释术语时却拒绝它?
2。使用 TypeApplications
而不是注释
我可以简单地通过注释 zero
项的类型来解决第一个问题:
-- This works
plusZero1 :: forall f a b . F f => f (Plus f a b)
plusZero1 = plus (zero :: f a) (zero :: f b)
但是,当项变大时,这有点笨拙。我想做的是使用 TypeApplications
。我试过这个:
-- This doesn't work
plusZero2 :: forall f a b . F f => f (Plus f a b)
plusZero2 = plus @f @a @b zero zero
但是 GHCi 抱怨:
• Expecting one more argument to ‘f’
Expected a type, but ‘f’ has kind ‘k -> *’
• In the type ‘f’
In the expression: plus @f @a @b zero zero
In an equation for ‘plusZero2’: plusZero2 = plus @f @a @b zero zero
• Relevant bindings include
plusZero2 :: f (Plus f a b) (bound at Main.hs:36:1)
奇怪的是,如果我首先按如下方式定义附加函数 plus'
和 zero'
,一切都会按预期运行:
zero' :: forall f a . F f => f a
zero' = zero
plus' :: forall f a b . F f => f a -> f b -> f (Plus f a b)
plus' = plus
-- This works fine
plusZero3 :: forall f a b . F f => f (Plus f a b)
plusZero3 = plus' @f @a @b zero' zero'
看来我还没有理解 TypeApplications
是如何与类型类方法一起工作的。
如何在不定义附加函数 plus'
和 zero'
的情况下使用具有 plus
和 zero
的类型应用程序?
- Inferred Types don't work?
在你的例子中GHC确实可以推断类型,但它不能接受你的签名。这可能看起来有悖常理,但如果您考虑一般情况,它确实有道理。
Plus f a b
是一个非单射类型族。对于 GHC 在类型检查时知道的所有内容,它可以定义为 Plus f a b = a
用于所有 f
、a
和 b
.
假设我们已经定义了一个术语(为了清楚起见,我添加了 forall
s)
foo :: forall f a b. F f => f (Plus f a b)
然后我们写
bar :: forall f a b. F f => f (Plus f a b)
bar = foo
这不应该进行类型检查 (!),因为它本质上是模棱两可的。作为人类的程序员可能希望编译器推断这些类型:
bar :: forall f a b. F f => f (Plus f a b)
bar = foo @f @a @b
但是,可能还有其他正确的推断类型!事实上,如果 Plus
是如上所述定义的,这也将键入 check:
bar :: forall f a b. F f => f (Plus f a b)
bar = foo @f @a @String
使用它,foo
将生成 f (Plus f a String)
,它与 f (Plus f a b)
相同,因此所有内容都会进行类型检查。由于程序员可能打算使用 @b
以外的其他东西,我们在这里停止报告类型错误的歧义。
从技术上讲,推理期间发生的事情是这样的:对 poltmorphic foo
的调用链接到新的未知类型变量:
bar :: forall f a b. F f => f (Plus f a b)
bar = foo @xf @xa @xb
然后,统一发生:foo @xf @xa @xb
的类型是 xf (Plus xf xa xb)
并与提供的签名统一以查找未知数:
xf (Plus xf xa xb) ~ f (Plus f a b)
我们应用统一算法:
xf ~ f
Plus xf xa xb ~ Plus f a b
所以我们找到未知 xf
的类型,代入我们得到:
xf ~ f
Plus f xa xb ~ Plus f a b
然而,我们到此为止。我们无法推断 xa ~ a
和 xb ~ b
,因为类型族不是单射的。
- Using TypeApplications instead of annotations
问题是有一个隐藏的类型 @k
参数,因为它出现在 class 中。使用 :t +v
显示所有 forall
的真实类型:
> :t +v plus
plus
:: forall k (f :: k -> *) (a :: k) (b :: k).
F f =>
f a -> f b -> f (Plus f a b)
传递 @k
也有效:
plusZero2 :: forall k (f :: k -> Type) a b . F f => f (Plus f a b)
plusZero2 = plus @k @f @a @b zero zero
或者,让编译器推断出 @k
:
plusZero2 :: forall f a b . F f => f (Plus f a b)
plusZero2 = plus @_ @f @a @b zero zero