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
。
答案 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 :: t
与t
签名中的其他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
或其他任何类型。