为什么不检查此类型?

时间:2018-07-20 16:09:51

标签: haskell

class Foo t where
  foo :: t

bar :: Binary t => t -> ()
bar = undefined

repro :: (Binary t, Foo t) => Proxy t -> ()
repro _proxy =
  bar (foo :: t)

编译器抱怨:

  • 无法推断出由于使用“ bar”引起的(二进制t0)       从上下文中:(二进制t,Foo t)         受类型签名的约束:                    repro :: forall。 (二进制t,Foo t)=>代理t->()

  • 无法推断出由于使用“ foo”而引起的(Foo t2)       从上下文中:(二进制t,Foo t)         受类型签名的约束:                    repro :: forall。 (二进制t,Foo t)=>代理t->()

特别是,令我惊讶的是,它没有看到我正在将t传递给bar,并创建了一个t0类型的var。 t2更加神秘,因为foo的类型明显为t

3 个答案:

答案 0 :(得分:4)

默认情况下,类型变量的作用域不是那样。函数签名中的t与函数主体中的t不同。您的代码与此等效:

repro :: (Binary t, Foo t) => Proxy t -> ()
repro _proxy =
  bar (foo :: a)

您需要启用ScopedTypeVariables扩展,并添加一个显式的forall t

{-# LANGUAGE ScopedTypeVariables #-}

repro :: forall t. (Binary t, Foo t) => Proxy t -> ()
repro _proxy =
  bar (foo :: t)

答案 1 :(得分:2)

您可能需要打开ScopedTypeVariables扩展名,然后使用

repro :: forall t. (Binary t, Foo t) => Proxy t -> ()
repro _proxy = bar (foo :: t)

否则,t中的foo :: tt签名中的其他repro不相关。本质上foo :: t等同于foo :: forall a. a

这可以说是Haskell定义中最不受欢迎的功能之一,ScopedTypeVariables非常受欢迎,因为它可以解决该问题。 (我认为默认情况下应启用该功能。)

答案 2 :(得分:2)

出于完整性考虑,也可以不进行扩展而处理。此处的关键技巧是编写一个类型超出其限制的函数,以将Proxy的type参数与Foo实例连接。所以:

-- most general possible type is Foo b => a -> b
fooForProxy :: Foo t => proxy t -> t
fooForProxy _proxy = foo

-- I've changed Proxy to proxy here because that's good practice, but everything
-- works fine with your original signature.
repro :: (Binary t, Foo t) => proxy t -> ()
repro = bar . fooForProxy

当然,现代的方法是甚至使用 more 扩展名来完全消除代理:

{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}

repro :: forall t. (Binary t, Foo t) => ()
repro = bar @t foo

调用repro将再次需要类型应用程序,例如repro @Int或其他任何类型。