我正在尝试使用对由其定义的功能之一返回的类型强制实施约束的类型类。但是函数的返回类型未捕获其类型变量中的约束。我想知道代码有什么问题或编码的正确方法是什么。 示例代码如下:
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代码中提取的代码段,Uniform
,Library
是opengl术语。
答案 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
(假设Bool
和String
均为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
,这意味着实现应强制使用更具体的类型。请注意,现在您需要在实例中同时指定a
和b
,所以:
instance Renderable Foo Bool where ...
instance Renderable Bar String where ...
我敢肯定,还有其他有效的方法可以解决这个问题。