我可以约束一个类型家庭吗?

时间:2013-01-03 04:36:52

标签: haskell

this recent answer of mine,我碰巧打开了这个老栗子(一个如此古老的程序,其中一半是由Leibniz在十七世纪写成的,并且在七十年代由我父亲写在计算机上)。为了节省空间,我将省略现代技术。

class Differentiable f where
  type D f :: * -> *

newtype K a     x  = K a
newtype I       x  = I x
data (f :+: g)  x  = L (f x)
                   | R (g x)
data (f :*: g)  x  = f x :&: g x

instance Differentiable (K a) where
  type D (K a) = K Void
instance Differentiable I where
  type D I = K ()
instance (Differentiable f, Differentiable g) => Differentiable (f :+: g) where
  type D (f :+: g) = D f :+: D g
instance (Differentiable f, Differentiable g) => Differentiable (f :*: g) where
  type D (f :*: g) = (D f :*: g) :+: (f :*: D g)

现在,这是令人沮丧的事情。我不知道如何规定D f必须本身可以区分。当然,这些实例都尊重这个属性,你可以编写一些有趣的程序,它们可以利用这种能力来区分一个仿函数,在越来越多的地方出现漏洞:Taylor扩展,那种事情。

我希望能够说出类似

的内容
class Differentiable f where
  type D f
  instance Differentiable (D f)

并要求检查实例声明是否具有必需实例的type定义。

也许更多平凡的东西,比如

class SortContainer c where
  type WhatsIn c
  instance Ord (WhatsIn c)
  ...

也很好。当然,这有一个fundep解决方法

class Ord w => SortContainer c w | c -> w where ...

但尝试Differentiable同样的伎俩似乎......好......惹人注意。

那么,有一个漂亮的解决方法可以让我重复可分辨性吗? (我想我可以构建一个表示GADT而且......而且有没有一种方法适用于类?)

有没有明显的障碍,我们应该能够在声明它们时要求对类型(以及我想,数据)系列的约束,然后检查实例是否满足它们?

4 个答案:

答案 0 :(得分:44)

当然,显而易见的是直接编写所需的约束:

class (Differentiable (D f)) => Differentiable (f :: * -> *) where
唉,GHC对此表示不满,并拒绝参与其中:

ConstrainTF.hs:17:1:
    Cycle in class declaration (via superclasses):
      Differentiable -> Differentiable
    In the class declaration for `Differentiable'

因此,正如通常情况下,当试图描述足以使GHC顽固不化的类型约束时,我们必须采取某种方式的狡猾欺骗。

回顾涉及类型hackery的GHC的一些相关特征:

  • 关于类型级别的不确定性是偏执的,与用户实际带来的不便不成比例。
  • 在考虑所有可用信息之前,它会愉快地致力于关于类和实例的决策。
  • 它将尽职尽责地检查你曾经欺骗过的任何事情。

这些是熟悉的旧仿伪通用实例的基本原理,其中类型与(~)进行事后统一,以改进某些类型hackery结构的类型推断行为。

然而,在这种情况下,我们需要以某种方式阻止GHC注意到类约束,而不是将类型信息隐藏在GHC之后,而不是类型信息 ...... heeeey,waaaitaminute ......完全不同的。

import GHC.Prim

type family DiffConstraint (f :: * -> *) :: Constraint
type instance DiffConstraint f = Differentiable f

class (DiffConstraint (D f)) => Differentiable (f :: * -> *) where
  type D f :: * -> *

用它自己的鞭子提升!

这也是真正的交易。正如您所希望的那样,这些都被接受了:

instance Differentiable (K a) where
  type D (K a) = K Void
instance Differentiable I where
  type D I = K ()

但如果我们提供一些废话:

instance Differentiable I where
  type D I = []

GHC向我们提供了我们希望看到的错误信息:

ConstrainTF.hs:29:10:
    No instance for (Differentiable [])
      arising from the superclasses of an instance declaration
    Possible fix: add an instance declaration for (Differentiable [])
    In the instance declaration for `Differentiable I'

当然,有一个小障碍,即:

instance (Differentiable f, Differentiable g) => Differentiable (f :+: g) where
  type D (f :+: g) = D f :+: D g

...结果证明不是有充分根据的,因为我们已经告诉GHC要检查,(f :+: g) Differentiable (D f :+: D g)Differentiable也不是type family DiffConstraint (f :: * -> *) :: Constraint type instance DiffConstraint (K a) = Differentiable (K a) type instance DiffConstraint I = Differentiable I type instance DiffConstraint (f :+: g) = (Differentiable f, Differentiable g) 结束(或根本)。

避免这种情况的最简单方法通常是在一堆明确的基本情况下进行样板处理,但是在这里,GHC似乎意图在实例上下文中出现instance (Differentiable (D f), Differentiable (D g)) => Differentiable (f :+: g) where type D (f :+: g) = D f :+: D g 约束时发散。我认为它不必要地以某种方式急切地检查实例约束,并且可能会因为另一层欺骗而分散注意力,但是我不能立即确定从哪里开始并且已经耗尽了我今晚午夜后类型hackery的能力。 / p>


关于#haskell的一些IRC讨论设法在GHC如何处理类上下文约束方面稍微调整了我的内存,看起来我们可以通过pickier约束族来修补一些东西。使用这个:

{{1}}

我们现在对sums的表现更加良好:

{{1}}

然而,递归情况不容易被产品分割,并且应用相同的更改只会在我收到上下文减少堆栈溢出而不是简单地挂在无限循环中时改进了事项。

答案 1 :(得分:20)

您最好的选择可能是使用constraints包来定义内容:

import Data.Constraint

class Differentiable (f :: * -> *) where
  type D f :: * -> *
  witness :: p f -> Dict (Differentiable (D f))

然后您可以在需要递归时手动打开字典。

这可以让你在Casey的答案中使用解决方案的一般形状,但不会让编译器(或运行时)在归纳时永远旋转。

答案 2 :(得分:10)

使用GHC 8中的新<p>Method 1: Use <code>justify-content: space-between</code> and <code>order-1</code></p> <div class="parent"> <div class="child" style="float:right"> Ignore parent? </div> <div>another child </div> </div> <hr> <p>Method 2: Use <code>justify-content: space-between</code> and reverse the order of divs in the mark-up</p> <div class="parent"> <div>another child </div> <div class="child" style="float:right"> Ignore parent? </div> </div>

UndecidableSuperclasses

作品。

答案 3 :(得分:8)

这可以通过爱德华建议的Dict的微小实现来完成。首先,让我们进行语言扩展和导入。

{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}

import Data.Proxy

TypeOperators仅用于您的示例问题。

Tiny Dict

我们可以制作Dict的微小实现。 Dict使用GADT和ConstraintKinds来捕获GADT的构造函数中的任何约束。

data Dict c where
    Dict :: c => Dict c  

withDictwithDict2通过GADT上的模式匹配重新引入约束。我们只需要能够用一两个约束来推理术语。

withDict :: Dict c -> (c => x) -> x
withDict Dict x = x

withDict2 :: Dict a -> Dict b -> ((a, b) => x) -> x
withDict2 Dict Dict x = x

无限可分类型

现在我们可以讨论无限可微类型,其衍生物也必须是可微分的

class Differentiable f where
    type D f :: * -> *
    d2 :: p f -> Dict (Differentiable (D f))
    -- This is just something to recover from the dictionary
    make :: a -> f a

d2接受该类型的代理,并恢复字典以获取二阶导数。代理允许我们轻松指定我们正在讨论的d2类型。我们可以通过应用d

来获取更深入的词典
d :: Dict (Differentiable t) -> Dict (Differentiable (D t))
d d1 = withDict d1 (d2 (pt (d1)))
    where
        pt :: Dict (Differentiable t) -> Proxy t
        pt = const Proxy

捕获dictonary

多项式函子类型,乘积,和,常数和零都是无限可微的。我们将为每种类型定义d2证人

data    K       x  = K              deriving (Show)
newtype I       x  = I x            deriving (Show)
data (f :+: g)  x  = L (f x)
                   | R (g x)
                                    deriving (Show)
data (f :*: g)  x  = f x :&: g x    deriving (Show)

零和常数不需要任何额外的知识来捕获它们的衍生物Dict

instance Differentiable K where
  type D K = K
  make = const K
  d2 = const Dict

instance Differentiable I where
  type D I = K
  make = I
  d2 = const Dict

Sum和product都要求两个组件衍生词的词典能够推导出它们衍生词典。

instance (Differentiable f, Differentiable g) => Differentiable (f :+: g) where
  type D (f :+: g) = D f :+: D g
  make = R . make
  d2 p = withDict2 df dg $ Dict
    where
        df = d2 . pf $ p
        dg = d2 . pg $ p
        pf :: p (f :+: g) -> Proxy f
        pf = const Proxy
        pg :: p (f :+: g) -> Proxy g
        pg = const Proxy

instance (Differentiable f, Differentiable g) => Differentiable (f :*: g) where
  type D (f :*: g) = (D f :*: g) :+: (f :*: D g)
  make x = make x :&: make x
  d2 p = withDict2 df dg $ Dict
    where
        df = d2 . pf $ p
        dg = d2 . pg $ p
        pf :: p (f :*: g) -> Proxy f
        pf = const Proxy
        pg :: p (f :*: g) -> Proxy g
        pg = const Proxy

恢复字典

我们可以恢复字典中的限制,否则我们就没有足够的信息来推断。 Differentiable f通常只允许使用make :: a -> f a,但不能使用make :: a -> D f amake :: a -> D (D f) a

make1 :: Differentiable f => p f -> a -> D f a
make1 p = withDict (d2 p) make

make2 :: Differentiable f => p f -> a -> D (D f) a
make2 p = withDict (d (d2 p)) make