GHC TypeLits没有值

试图设计一个类型驱动的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并没有真正的帮助。 任何方式来让这个工作无论如何?


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

generic :: KnonwSymbol s => IO ()

此类签名没有=> s权限,因此未能进行歧义检查。 它不能真正发挥作用,因为没有地方指定什么s应该是,当你使用它。

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

您需要的是提供不依赖于类型同义词的相关类型级别符号的某种方式。 但首先,你需要启用ScopedTypeVariables并添加forall到的签名generic以确保s在类型签名和sProxy :: Proxy s是相同的。

有两种可能性:

  • 更改Printernewtype ,当你使用它解开它:

    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并且让调用者代替他们想要的任何东西。


  • 这是错误的:

    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)
    

    然而,还有其他问题,正如吉洪维斯在答案中指出的那样。

    链接地址: http://www.djcxy.com/p/90823.html

    上一篇: GHC TypeLits without values

    下一篇: Android camera code