Mik*_*cki 7 haskell types existential-type gadt
在我正在处理的HLearn库中,我有一些容器数据类型如下所示:
data (Model params model) => Container' params model = Container'
{ baseparams :: params
, basemodel :: model
}
Run Code Online (Sandbox Code Playgroud)
问题是,这种类型是尴尬,因为使用params并model都从独特的相互决定的:
class Model params model | params -> model, model -> params
Run Code Online (Sandbox Code Playgroud)
因此,如果我在指定类型时不必指定它们,那将会更方便.编译器应该能够自动为我完成.
我解决这个问题的想法是创建一个使用存在量化的类型别名:
type Container model = forall params . (Model params model) => Container' params model
Run Code Online (Sandbox Code Playgroud)
但这不起作用.如果我Container'像往常一样制作一个实例,一切正常:
data ContainerParams params = ContainerParams params
instance (Model params model) => Model (ContainerParams params) (Container' params model)
Run Code Online (Sandbox Code Playgroud)
但是当我使用我的Container类型时:
instance (Model params model) => Model (ContainerParams params) (Container model)
Run Code Online (Sandbox Code Playgroud)
ghc爆炸:
非法多态或限定类型:容器模型在`Model(ContainerParams params)(容器模型)的实例声明中
我不知道这个错误信息的含义.是否有可能以某种方式修复我的解决方案,使Container您不必指定参数?
编辑:我应该注意,将forall语句移动到Container'声明中似乎需要一堆unsafeCoerces,所以这似乎是一个糟糕的解决方案.
此外,我可以改变type Container进入data Container并使事情发挥作用,但这需要我重新声明Conatiner'属于的所有实例,我不想这样做.我有许多不同的类型遵循这种模式,所以似乎应该有一种通用的方法来解决这个问题.
Phi*_* JF 12
我不确定你是否想要普遍或存在量化.无论哪种方式,最好将其包装成新鲜的类型.
强烈建议:不要在普通数据类型上使用约束头.他们会让你的生活更加艰难,而不是更容易.他们没有任何用处.
{-# LANGUAGE GADTs #-}
data Container' params model = Container'
{ baseparams :: params
, basemodel :: model
}
data Container p m where
Container :: Model params model => Container' params model -> Container params model
Run Code Online (Sandbox Code Playgroud)
{-# LANGUAGE Rank2Types #-}
data Container' params model = Container'
{ baseparams :: params
, basemodel :: model
}
newtype Container model = Container (forall params . Model params model => Container' params model)
Run Code Online (Sandbox Code Playgroud)
您不能在类型类实例中具有通用或限定类型.所以
instance Model (ContainerParams params) (Container model)
Run Code Online (Sandbox Code Playgroud)
由于类型同义词扩展为,因此不被允许
instance Model (ContainerParams params) (forall ...)
Run Code Online (Sandbox Code Playgroud)
在我GADT的解决方案,我都处理param并model作为参数.这是因为需要知道的重要事项:功能依赖性不会融合!并且,编译器不会假定它们为了类型检查而汇合.函数依赖性仅用于指导约束求解器(在某种程度上它们类似于prolog中的"cut"之类的额外逻辑构造).如果你想要融合,请使用TypeFamilies.
class Model model where
type Param model
...
Run Code Online (Sandbox Code Playgroud)
或者这样做很棒的方式
class (model ~ (TheModel param),param ~ (TheParam model)) => Model model param where
type TheModel param
type TheParam model
Run Code Online (Sandbox Code Playgroud)
与fundep具有相同的双向性.而且,这会让你把你的存在实例写成
data Container model where
Container :: Model model param => Container' model param -> Container model
Run Code Online (Sandbox Code Playgroud)
然后你可以做一些事情,比如组合两个相同model类型的容器,知道存在量化params将匹配.使用它,您可以定义
data HasParam model where
HasParam :: Model model param => HasParam model
data GADTContainer model where
GADTContainer :: Model model param => Container' model param -> GADTContainer model
newtype NewContainer model
= NewContainer (forall param. Model model param => Container' model param)
Run Code Online (Sandbox Code Playgroud)
然后元组(HasParam model, NewContainer model)可证明是同构的,GADTContainer model这也解释了这些类型之间的关系
无论如何,一旦你有了这个,你可以使用适当的包装类型定义你的实例.