有没有一种方便的方法来使用 mono-traversable 构造更大类型级别的 Peano 数?

Is there a convenient way to construct larger type level Peano numbers using mono-traversable?

mono-traversable 包使用 MinLen 类型级别的 Peano 数。我可以使用链式 Succs:

构造它们
toMinLen [1,2,3] :: Maybe (MinLen (Succ (Succ Zero)) [Int])

但这很快就会失控:

toMinLen [1,2,3] :: Maybe (MinLen (Succ (Succ (Succ (Succ (Succ Zero))))) [Int])

有没有方便的方法来构造更大的皮亚诺数?我看到 GHC 有一个 TypeLiterals 扩展,但我不确定我是否可以在这里使用它。或者,我可以制作类似的同义词:

type One = Succ Zero
type Two = Succ One

等等;类似的东西已经存在于某处了吗?

您可以移植 type-natural's QuasiQuoters.

这种东西有一个包:type-level。这有点吓人,我还没有真正探索过它。但是你不需要那么多的力量,所以你可以自己动手做一些简单的事情。

如果您愿意使用 UndecidableInstances,事情就很简单了。大致是这样的(我不知道那个库中自然是如何定义的;如果它不使用 DataKinds,你可能必须使用 * 类型而不是 Nat kind,你可能不得不写 'Succ'Zero 而不是 SuccZero -- 这方面我不太清楚):

{-# LANGUAGE UndecidableInstances, TypeFamilies, DataKinds, TypeOperators #-}

module TAR where

-- You wouldn't actually use this line, because the library
-- provides the naturals
data Nat = Zero | Succ Nat

-- Digits and Ten to get things started
type One = Succ Zero
type Two = Succ One
type Three = Succ Two
type Four = Succ Three
type Five = Succ Four
type Six = Succ Five
type Seven = Succ Six
type Eight = Succ Seven
type Nine = Succ Eight
type Ten = Succ Nine

type family Plus (a::Nat) (b::Nat) where
  Plus Zero n = n
  Plus (Succ m) n = Plus m (Succ n)

type family Times (a::Nat) (b::Nat) where
  Times Zero n = Zero
  Times (Succ m) n = Plus n (Times m n)

type Decimal (a::[Nat]) = Decimal' a Zero

type family Decimal' (a::[Nat]) (n::Nat) where
  Decimal' '[] n = n
  Decimal' (a ': bs) n = Decimal' bs (Plus a (Times Ten n))

然后你可以这样写

Decimal '[One, Two, Five]

表示 125。

TypeLits 非常适合类型级数字。此外,仅将其用于语法糖并保持底层特定于库的实现不变也很容易。

{-# LANGUAGE
  UndecidableInstances, TypeFamilies,
  DataKinds, TypeOperators #-}

import qualified GHC.TypeLits as TL

data Nat = Zero | Succ Nat

newtype MinLen (n :: Nat) a = MinLen a

我们必须定义一个将文字转换为数字类型的类型族:

type family Lit (n :: TL.Nat) :: Nat where
  Lit 0 = Zero
  Lit n = Succ (Lit (n TL.- 1))

现在您可以在需要 Nat 文字时使用 Lit n。在 GHCi 中:

>:kind! MinLen (Lit 3)
MinLen (Lit 3) :: * -> *
= MinLen ('Succ ('Succ ('Succ 'Zero)))