Haskell 我可以约束类型族吗?
年,我碰巧打开了这个旧的栗子(一个如此古老的程序,其中一半是17世纪莱布尼茨写的,70年代是我爸爸在电脑上写的)。为了节省空间,我将省去现代的部分Haskell 我可以约束类型族吗?,haskell,Haskell,年,我碰巧打开了这个旧的栗子(一个如此古老的程序,其中一半是17世纪莱布尼茨写的,70年代是我爸爸在电脑上写的)。为了节省空间,我将省去现代的部分 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)
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)
现在,令人沮丧的是。我不知道如何规定df本身必须是可微的。当然,这些例子尊重这个属性,你可以编写一些有趣的程序,它们利用不断区分函子的能力,在越来越多的地方打洞:泰勒展开式,诸如此类的东西
我想说些类似的话
class Differentiable f where
type D f
instance Differentiable (D f)
并要求检查实例声明是否具有必要实例存在的类型
定义
也许更普通的东西,比如
class SortContainer c where
type WhatsIn c
instance Ord (WhatsIn c)
...
也很好。当然,这是fundep的解决办法
class Ord w => SortContainer c w | c -> w where ...
但是对可微性尝试同样的技巧似乎。。。好。。。内卷
那么,有没有一个巧妙的解决办法,让我重复微分?(我想我可以构建一个表示GADT和……但是有没有一种方法可以与类一起工作?)
有人建议我们在声明类型(我想还有数据)族时,应该能够要求对其进行约束,然后检查实例是否满足这些约束,这是否存在明显的障碍?当然,显而易见的是直接编写所需的约束:
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顽固不化的类型约束时,我们经常会遇到这种情况,我们必须求助于某种暗箱操作
回顾GHC涉及黑客类型的一些相关特征:
- 它偏执于类型级别的非终止,与它给用户带来的实际不便不成比例
- 在考虑所有可用信息之前,它将愉快地致力于关于类和实例的决策
- 它会尽职尽责地检查你欺骗它的任何事情
这些都是常见的旧仿制泛型实例背后的迂回原则,在这些实例中,类型在事后与(~)
统一,以改进某些类型黑客构造的类型推断行为
然而,在这种情况下,我们需要以某种方式防止GHC注意到一个类约束,这是一种完全不同的。。。嘿,等一下
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)
是可微的
,那么(df:+:dg)
,结果就不好(或根本不好)
避免这种情况的最简单方法通常是在一堆显式的基本情况下进行样板分析,但在这里,GHC似乎有意在实例上下文中出现可微的
约束时进行发散。我认为它在某种程度上对检查实例约束有着不必要的渴望,可能会因为另一层欺骗而分心足够长的时间,但我不知道从哪里开始,并且已经耗尽了我今晚午夜后类型黑客的能力
IRC关于#haskell的一点讨论让我想起了GHC如何处理类上下文约束,似乎我们可以通过更挑剔的约束族来修补一些问题。使用此选项:
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)
现在,我们有了一个表现更好的求和递归:
instance (Differentiable (D f), Differentiable (D g)) => Differentiable (f :+: g) where
type D (f :+: g) = D f :+: D g
然而,对于产品来说,递归情况不可能如此容易地一分为二,在那里应用相同的更改只会改善问题,因为我收到了上下文缩减堆栈溢出,而不是简单地挂在一个无限循环中。您最好的选择可能是使用约束
包定义一些东西:
import Data.Constraint
class Differentiable (f :: * -> *) where
type D f :: * -> *
witness :: p f -> Dict (Differentiable (D f))
然后,您可以在需要递归时手动打开字典
这将允许您在Casey的答案中使用解决方案的一般形式,但不会让编译器(或运行时)在归纳中永远旋转。这可以通过Edward建议的相同方式来实现,只需一个小小的Dict
。首先,让我们把语言扩展和导入放在一边
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
import Data.Proxy
TypeOperators
仅用于您的示例问题
小小字典
我们可以自己制作Dict
的微型实现Dict
使用GADT和ConstraintKinds
捕获GADT构造函数中的任何约束
data Dict c where
Dict :: c => Dict c
使用DICT
和使用DICT2
在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
捕捉口述
多项式函子类型、乘积、和、常数和零都是无限可微的。我们将为每种类型定义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
求和和和积都要求其分量导数的字典能够进行推断
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
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
class Differentiable (D f) => Differentiable (f :: Type -> Type) where