我本来可以发誓我最近看到了一篇关于此的文章,但我找不到它。
我正在尝试创建一个类型来对数字mod n
进行二进制编码,但为了这样做,我需要能够在自然类型级别上编写谓词:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
module Modulo where
-- (pseudo-)binary representation of
-- numbers mod n
--
-- e.g. Mod Seven contains
-- Zero . Zero . Zero $ Stop
-- Zero . Zero . One $ Stop
-- Zero . One . Zero $ Stop
-- Zero . One . One $ Stop
-- One . Zero . Zero $ Stop
-- One . Zero . One $ Stop
-- One . One $ Stop
data Mod n where
Stop :: Mod One
Zero :: Split n => Mod (Lo n) -> Mod n
One :: Split n => Mod (Hi n) -> Mod n
-- type-level naturals
data One
data Succ n
type Two = Succ One
-- predicate to allow us to compare
-- natural numbers
class Compare n n' b | n n' -> b
-- type-level ordering
data LT
data EQ
data GT
-- base cases
instance Compare One One EQ
instance Compare One (Succ n) LT
instance Compare (Succ n) One GT
-- recurse
instance Compare n n' b => Compare (Succ n) (Succ n') b
-- predicate to allow us to break down
-- natural numbers by their bit structure
--
-- if every number mod n can be represented in m bits, then
class Split n where
type Lo n -- number of values mod n where the m'th bit is 0
type Hi n -- number of values mod n where the m'th bit is 1
-- base case, n = 2
-- for this, we only need m=1 bit
instance Split Two where
type Lo Two = One -- 0 mod 2
type Hi Two = One -- 1 mod 2
-- recurse
-- if (Lo n) == (Hi n), then n = 2^m, so
-- the values mod (n+1) will require one extra bit
instance (Split n, Compare (Lo n) (Hi n) EQ) => Split (Succ n) where
type Lo (Succ n) = n -- all the numbers mod n will be prefixed by a 0
type Hi (Succ n) = One -- and one extra, which will be 10...0
-- recurse
-- if (Lo n) > (Hi n), then n < 2^m, so
-- the values mod (n+1) still only require m bits
instance (Split n, Compare (Lo n) (Hi n) GT) => Split (Succ n) where
type Lo (Succ n) = Lo (n) -- we've got the same number of lower values
type Hi (Succ n) = Succ (Hi n) -- and one more higher value
我当前的实现会吐出一堆编译器错误:
Nat.hs:60:8:
Conflicting family instance declarations:
type Lo Two -- Defined at Nat.hs:60:8-9
type Lo (Succ n) -- Defined at Nat.hs:74:8-9
Nat.hs:61:8:
Conflicting family instance declarations:
type Hi Two -- Defined at Nat.hs:61:8-9
type Hi (Succ n) -- Defined at Nat.hs:75:8-9
Nat.hs:66:10:
Duplicate instance declarations:
instance (Split n, Compare (Lo n) (Hi n) EQ) => Split (Succ n)
-- Defined at Nat.hs:66:10-62
instance (Split n, Compare (Lo n) (Hi n) GT) => Split (Succ n)
-- Defined at Nat.hs:73:10-62
Nat.hs:67:8:
Conflicting family instance declarations:
type Lo (Succ n) -- Defined at Nat.hs:67:8-9
type Lo (Succ n) -- Defined at Nat.hs:74:8-9
Nat.hs:68:8:
Conflicting family instance declarations:
type Hi (Succ n) -- Defined at Nat.hs:68:8-9
type Hi (Succ n) -- Defined at Nat.hs:75:8-9
这让我觉得如果我认为他们的谓词存在冲突,我就会把我的谓词写错了。
我该怎么办呢?
答案 0 :(得分:14)
冲突问题很简单。 rules for overlapping type families非常简单:
如果重叠实例的右侧与重叠类型重合,则单个程序中使用的类型系列的实例声明可能只会重叠。更正式地说,如果存在替换使得实例的左侧在语法上相同,则两个实例声明重叠。只要是这种情况,实例的右侧在同一替换下也必须在语法上相等。
请注意,它指定语法相等。考虑这两个实例:
instance Split Two where
type Lo Two = One -- 0 mod 2
type Hi Two = One -- 1 mod 2
instance Split (Succ n) where
type Lo (Succ n) = Lo (n)
type Hi (Succ n) = Succ (Hi n)
Two
被定义为Succ One
,为了语法上的平等,普通类型同义词被扩展,所以这些在左边是相同的;但右边不是,因此错误。
您可能已经注意到我从上面的代码中删除了类上下文。更深层次的问题,也许是您不期望发生上述冲突的原因,是重复的实例确实 冲突重复。出于实例选择的目的,类上下文一如既往地被忽略,如果内存服务于我,那么对于相关类型族来说这是一个双重的,这对于非关联类型族来说很大程度上是语法上的便利,并且可能不会受到类的约束。期望的。
现在,显然这两个实例应该是不同的,并且您希望根据使用Compare
的结果在它们之间进行选择,因此该结果必须是类型类的参数,而不仅仅是约束。你也在这里混合具有功能依赖性的类型族,这是不必要的尴尬。所以,从顶部开始,我们将保留基本类型:
-- type-level naturals
data One
data Succ n
type Two = Succ One
-- type-level ordering
data LT
data EQ
data GT
将Compare
函数重写为类型系列:
type family Compare n m :: *
type instance Compare One One = EQ
type instance Compare (Succ n) One = GT
type instance Compare One (Succ n) = LT
type instance Compare (Succ n) (Succ m) = Compare n m
现在,为了处理条件,我们需要某种“流控制”类型族。我将在这里定义一些更为通用的东西,用于启发和启发;根据品味专注。
我们将给它一个谓词,一个输入和两个分支来选择:
type family Check pred a yes no :: *
我们需要一个谓词来测试Compare
的结果:
data CompareAs a
type instance (CompareAs LT) LT yes no = yes
type instance (CompareAs EQ) LT yes no = no
type instance (CompareAs GT) LT yes no = no
type instance (CompareAs LT) EQ yes no = no
type instance (CompareAs EQ) EQ yes no = yes
type instance (CompareAs GT) EQ yes no = no
type instance (CompareAs LT) GT yes no = no
type instance (CompareAs EQ) GT yes no = no
type instance (CompareAs GT) GT yes no = yes
这是一套非常繁琐乏味的定义,并且在比较较大的类型值集时,预测非常严峻。存在更多可扩展的方法(伪类标记和自然生物是一种明显有效的解决方案),但这有点超出了这个答案的范围。我的意思是,我们只是在这里进行类型级计算,让我们不要荒谬或其他任何东西。
在这种情况下更简单的是简单地在比较结果上定义案例分析函数:
type family CaseCompare cmp lt eq gt :: *
type instance CaseCompare LT lt eq gt = lt
type instance CaseCompare EQ lt eq gt = eq
type instance CaseCompare GT lt eq gt = gt
我现在会使用后者,但如果你想在其他地方使用更复杂的条件,那么通用方法就会变得更有意义。
反正。我们可以将... er,Split
类拆分为无关联类型族:
data Oops
type family Lo n :: *
type instance Lo Two = One
type instance Lo (Succ (Succ n))
= CaseCompare (Compare (Lo (Succ n)) (Hi (Succ n)))
Oops -- yay, type level inexhaustive patterns
(Succ n)
(Lo (Succ n))
type family Hi n :: *
type instance Hi Two = One
type instance Hi (Succ (Succ n))
= CaseCompare (Compare (Lo (Succ n)) (Hi (Succ n)))
Oops -- yay, type level inexhaustive patterns
One
(Succ (Hi (Succ n)))
这里最重要的一点是(Succ (Succ n))
的(看似多余的)使用 - 原因是需要两个Succ
构造函数来区分参数Two
,因此避免你看到的冲突错误。
请注意,为了简单起见,我已将所有内容都移到此处键入系列,因为它完全是类型级别的。如果您还希望根据上述计算(包括间接地,通过Mod
类型)处理不同的值,则可能需要添加适当的类定义,因为这些是根据类型选择术语所必需的而不仅仅是根据类型选择类型。