GHC TypeLits没有值

时间:2015-02-17 22:10:27

标签: haskell ghc

尝试设计一个类型驱动的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并不能提供帮助。无论如何都有办法让这个工作吗?

2 个答案:

答案 0 :(得分:9)

在类型检查期间,类型同义词(用type定义)将替换为它们的定义。问题是Printer在其定义中没有引用s,这会导致以下约束:

generic :: KnonwSymbol s => IO ()

此类型签名没有s的{​​{1}}权限,因此无法进行歧义检查。它无法真正起作用,因为在您使用它时,无处可指定=>应该是什么。

不幸的是,GHC在错误消息中表示类型同义词的方式不一致。有时它们会被扩展,有时它们会被保留。具有讽刺意味的是,我认为错误消息的改进使得这个特定错误更难以追踪:通常,根据您定义的类型表达错误更清楚,但这里隐藏了歧义的原因。

您需要的是提供不依赖于类型同义词的相关类型级符号的某种方式。但首先,您需要启用s并在ScopedTypeVariables的签名中添加forall,以确保类型签名中的generics中的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在答案中指出的那样。