尝试设计一个类型驱动的API,我一直在努力尝试以下工作(使用更复杂的代码/尝试,这被删除到澄清我所做的最低要求。我正在寻找):
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
module Main where
import Data.Proxy
import GHC.TypeLits
type Printer (s :: Symbol) = IO ()
concrete :: Printer "foo"
concrete = generic
generic :: KnownSymbol s => Printer s
generic = putStrLn (symbolVal (Proxy :: Proxy s))
main :: IO ()
main = concrete
此程序会打印“foo”,但不会:
Could not deduce (KnownSymbol s0)
arising from the ambiguity check for ‘generic’
from the context (KnownSymbol s)
bound by the type signature for
generic :: KnownSymbol s => Printer s
at test5.hs:14:12-37
The type variable ‘s0’ is ambiguous
In the ambiguity check for:
forall (s :: Symbol). KnownSymbol s => Printer s
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
In the type signature for ‘generic’:
generic :: KnownSymbol s => Printer s
启用AllowAmbiguousTypes
并不能提供帮助。无论如何都有办法让这个工作吗?
答案 0 :(得分:9)
在类型检查期间,类型同义词(用type
定义)将替换为它们的定义。问题是Printer
在其定义中没有引用s
,这会导致以下约束:
generic :: KnonwSymbol s => IO ()
此类型签名没有s
的{{1}}权限,因此无法进行歧义检查。它无法真正起作用,因为在您使用它时,无处可指定=>
应该是什么。
不幸的是,GHC在错误消息中表示类型同义词的方式不一致。有时它们会被扩展,有时它们会被保留。具有讽刺意味的是,我认为错误消息的改进使得这个特定错误更难以追踪:通常,根据您定义的类型表达错误更清楚,但这里隐藏了歧义的原因。
您需要的是提供不依赖于类型同义词的相关类型级符号的某种方式。但首先,您需要启用s
并在ScopedTypeVariables
的签名中添加forall
,以确保类型签名中的generic
和s
中的s
Proxy :: Proxy s
是一样的。
有两种可能性:
将Printer
更改为newtype
并在您使用时将其解包:
newtype Printer (s :: Symbol) = Printer { runPrinter :: IO () }
generic :: forall s. KnownSymbol s => Printer s
generic = Printer $ putStrLn (symbolVal (Proxy :: Proxy s))
main = runPrinter generic
将额外的Proxy
参数传递给generic
,就像symbolVal
一样:
concrete :: Printer "foo"
concrete = generic (Proxy :: Proxy "foo")
generic :: forall proxy s. KnownSymbol s => proxy s -> IO ()
generic _ = putStrLn (symbolVal (Proxy :: Proxy s))
将proxy
作为一个类型变量是一个简洁的习惯用法,它让你不依赖于Data.Proxy
,让调用者可以代替他们想要的任何东西。
答案 1 :(得分:2)
这是错误的:
generic :: KnownSymbol s => Printer s
generic = ...(Proxy :: Proxy s)
最后一个s
与其上方的s
无关。它在本地隐式普遍量化,如在顶级类型注释中。代码实际上意味着
generic :: KnownSymbol s => Printer s
generic = ...(Proxy :: forall z. Proxy z)
要解决上述问题,请启用ScopedTypeVariables
并使用
-- the explicit forall makes s available below
generic :: forall s. KnownSymbol s => Printer s
generic = ...(Proxy :: Proxy s)
还有其他问题,正如Tikhon Jelvis在答案中指出的那样。