在未实现类型

时间:2018-08-08 18:46:29

标签: haskell

我正在尝试使用对由其定义的功能之一返回的类型强制实施约束的类型类。但是函数的返回类型未捕获其类型变量中的约束。我想知道代码有什么问题或编码的正确方法是什么。 示例代码如下:

  data State a = State {
    uniform :: a
  }
  class Renderable a where
    render :: (Uniform b) => Int -> a -> State b

  library :: (Uniform a) => a -> IO ()
  -- some implementation

  draw :: (Renderable a) => a -> IO ()
  draw renderable = do
    let state = render 0 renderable
    _ <- library (uniform state)

在上面的代码段中,render函数试图强制State中的uniform属性遵守类约束Uniform。运行代码时,出现错误

 Could not deduce (Uniform a5) arising from a use of ‘draw’
  from the context: (Renderable r, Uniform a)
  bound by the type signature for:
               draw :: forall r a.
                       (Renderable r, Uniform a) =>
                       Int -> Renderable r -> IO ()

考虑到这一点,我能够理解,因为draw的类型仅使用Renderable,而Renderable在其中没有类型为Uniform的参数其类型签名,编译器无法完全验证流。但是我想知道,为什么编译器在测试draw的类型签名时不能忽略该问题,并且基本上取决于它会知道实现Renderable的类型是否一定要提供一个值的事实。 uniform作为State的一部分,它可以在实现站点而不是用法中验证类型正确性。

PS:这是从OpenGL代码中提取的代码段,UniformLibrary是opengl术语。

2 个答案:

答案 0 :(得分:2)

这是一种适合您的技术。我written about this多年前(在稍有不同的情况下,但想法是相同的),但我仍然坚持。

首先,取景。如果我们明确写出render的签名,我们将:

render :: forall b. Uniform b => Int -> a -> State b

也就是说,render的 caller 选择类型b。在我看来,您的意图更像是这种伪Haskell *:

render :: exists b. (Uniform b) & Int -> a -> State b

被呼叫者在其中选择类型。也就是说,render的不同实现可以选择不同的类型b来返回,只要它们是统一的即可。

这可能是表达它的一种好方法,除了Haskell不直接支持存在量化。您可以创建包装器数据类型以对其进行仿真

data SomeUniform where
    SomeUniform :: Uniform a => a -> SomeUniform

进行签名

render :: Int -> a -> SomeUniform
我认为

具有您要寻找的属性。 但是 SomeUniform类型和Uniform类型类很可能是多余的。您在评论中说Uniform类型类看起来像这样:

class Uniform a where
    library :: a -> IO ()

让我们考虑一下这个问题:假设我们有一个SomeUniform,即我们有一个类型为a的值,除了它是{{1 }} typeclass。 Uniform有可能做什么?只有一种方法可以从x中获取任何信息,那就是对其调用x。因此,实质上,library类型唯一要做的就是携带SomeUniform方法,稍后再调用。整个 existential / typeclass 毫无意义,我们最好将其折叠为简单的数据类型

library

,您的data Uniform = Uniform { library :: IO () } 方法变为:

render

太漂亮了,不是吗?如果render :: Int -> a -> Uniform 类型类中有更多方法,它们将成为此数据类型的其他字段(其类型可能是函数,可能需要一些时间才能习惯)。您拥有类型类的类型和实例的地方,例如

Uniform

您现在也可以摆脱数据类型,而只需使用函数代替构造函数

data Thingy = Thingy String
-- note the constructor type Thingy :: String -> Thingy

instance Uniform String where
    library (Thingy s) = putStrLn $ "thingy " ++ s

(如果由于其他原因而无法摆脱数据类型,则可以提供转换函数代替thingy :: String -> Uniform thingy s = Uniform { library = putStrLn $ "thingy " ++ s }

这里的原理是,您可以用其观察值的集合替换一个存在性类型,如果这样做的话通常会很不错。


* 我的伪Haskell uniformThingy :: Thingy -> Uniform&的双重角色,除了起定量存在的字典作用外,基本上扮演着相同的角色。 =>表示一旦 caller 提供了字典c => t,则返回类型c,而t意味着 callee < / em>提供字典c & t 类型c

答案 1 :(得分:1)

您似乎希望能够定义render来为Renderable的每个实现返回不同的独特类型,只要该类型为Uniform

instance Renderable Foo where
  render _ _ = State True

instance Renderable Bar where
  render _ _ = State "mothman"

instance Renderable Baz where
  render _ _ = State 19

因此,如果使用render调用Foo,它将返回State Bool,但是如果使用Bar调用,它将返回State String (假设BoolString均为Uniform)。这不是它的工作方式,如果尝试这样实例化,则会出现类型不匹配错误。

render :: (Uniform b) => Int -> a -> State b表示返回Uniform b => State b。如果这就是您的类型签名,那么您的实现必须或多或少地具体;您的实现必须能够返回任何类型Uniform b => State b的值。如果无法做到这一点,那么任何请求特定类型的返回值的代码都将无法获得正确的类型,并且事情将会以类型系统应该能够防止的方式发生故障。

让我们看一个不同的例子:

class Collection t where
  size :: Num i => t a -> i

假设有人想运行此size函数,并以Double的形式获得结果。他们可以这样做,因为size的任何实现都必须能够返回任何类型的Num类,因此调用者可以始终指定他们想要的类型。如果允许您编写始终返回Integer的实现,那么将不再可能。


我想做您想做的事,您需要类似FunctionalDependencies的东西。有了这个,您的课程可以像这样:

class Uniform b => Renderable a b | a -> b where
  render :: Int -> a -> State b

| a -> b”告诉类型检查器应根据调用者提供的类型b来确定类型a。这使调用者无法选择自己的b,这意味着实现应强制使用更具体的类型。请注意,现在您需要在实例中同时指定ab,所以:

instance Renderable Foo Bool where ...
instance Renderable Bar String where ...

我敢肯定,还有其他有效的方法可以解决这个问题。