Haskell/GHC: 如何在类型级自然数上编写谓词

9

我记得最近看到一篇关于这个的文章,但是我找不到它了。

我正在尝试制作一个类型来对数字模 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

这让我想到,如果它认为谓词冲突了,那么我写谓词的方式可能有误。如何正确地编写谓词呢?

你所说的“naturals”,是指“自然数”吗? - Charles
1
类型族和重叠实例目前不能同时使用。请查看http://hackage.haskell.org/trac/ghc/ticket/4259。 - Nathan Howell
1个回答

14
冲突问题很简单。有关类型族重叠规则非常简单:

在单个程序中使用的类型族的实例声明只能重叠,如果重叠实例的右侧对于重叠类型是相同的。 更正式地说,如果存在一种替换使得实例的左侧在语法上相同,则两个实例声明会重叠。每当这种情况发生时,在相同的替换下实例的右侧也必须在语法上相等。

请注意,它指定了语法上相等。考虑以下两个实例:

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

我现在将使用后者,但如果您希望在其他地方使用更复杂的条件语句,则通用方法开始变得更加合理。

无论如何,我们可以将...嗯,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类型--您可能需要添加适当的类定义,因为这些定义是基于类型选择术语所必需的,而不仅仅是基于类型选择类型。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接