Haskell的Idris是Fin的首选替代品

时间:2017-05-17 09:40:13

标签: haskell dependent-type idris

我想要一个可以包含值0到n的类型,其中n存在于类型级别。

我正在尝试类似的事情:

import GHC.TypeLits
import Data.Proxy

newtype FiniteNat n = FiniteNat { toInteger :: Integer }

smartConstructFiniteNat :: (KnownNat n) => Proxy n -> Integer -> Maybe (FiniteNat (Proxy n))
smartConstructFiniteNat pn i 
  | 0 <= i && i < n = Just (FiniteNat i)
  | otherwise       = Nothing
  where n = natVal pn

基本上起作用,但它并不是真的令人满意。是否有&#34;标准&#34;解决方案,甚至是图书馆来实现这个目标?关于依赖类型列表长度有很多大惊小怪,但我无法找到完全符合此类的东西。另外 - 我假设使用GHC.TypeLits是必要的,因为我的n可以采用相当大的值,因此归纳定义可能会非常慢。

2 个答案:

答案 0 :(得分:11)

你可以直接将伊德里斯的Fin翻译成通常的Haskell混杂的类型依赖类型。

data Fin n where
    FZ :: Fin (S n)
    FS :: Fin n -> Fin (S n)

(!) :: Vec n a -> Fin n -> a
(x :> xs) ! FZ = x
(x :> xs) ! (FS f) = xs ! f

使用TypeInType,你甚至可以拥有单身Fin

data Finny n (f :: Fin n) where
    FZy :: Finny (S n) FZ
    FSy :: Finny n f -> Finny (S n) (FS f)

这允许您伪装依赖于运行时内容的量化,例如

type family Fin2Nat n (f :: Fin n) where
    Fin2Nat (S _) FZ = Z
    Fin2Nat (S n) (FS f) = S (Fin2Nat n f)

-- tighten the upper bound on a given Fin as far as possible
tighten :: Finny n f -> Fin (S (Fin2Nat n f))
tighten FZy = FZ
tighten (FSy f) = FS (tighten f)

但是,呃,有必要在值和类型级别复制所有内容,写出所有类型的变量(n)会变得相当繁琐。

如果您确定需要Fin的高效运行时表示,基本上可以完成您在问题中所做的操作:将计算机Int填入newtype并使用它的大小的幻像类型。但是,作为图书馆实施者,您有责任确保Int符合约束!

newtype Fin n = Fin Int

-- fake up the constructors
fz :: Fin (S n)
fz = Fin 0
fs :: Fin n -> Fin (S n)
fs (Fin n) = Fin (n+1)

此版本缺少真正的GADT构造函数,因此您无法使用模式匹配来操纵类型相等。您必须使用unsafeCoerce自己完成。您可以以fold的形式为客户端提供类型安全的界面,但他们必须愿意以更高阶的方式编写所有代码,并且(因为fold是一个catamorphism)它更难以一次看多层。

-- the unsafeCoerce calls assert that m ~ S n
fold :: (forall n. r n -> r (S n)) -> (forall n. r (S n)) -> Fin m -> r m
fold k z (Fin 0) = unsafeCoerce z
fold k z (Fin n) = unsafeCoerce $ k $ fold k z (Fin (n-1))

哦,你不能使用Fin2Nat的这种表示来进行类型级别计算(就像我们使用上面Fin所做的那样),因为类型级别Int不允许归纳

对于它的价值,伊德里斯的Fin与上面的GADT效率一样低效。文档包含the following caveat

  

Fin用于算术可能不是一个好主意,并且它们在运行时会非常低效。

我听说有关Idris的未来版本能够发现“Nat类型”样式数据类型(如Fin)并自动删除校样并将值打包到机器整数中的声音,但据我所知,我们还没有。

答案 1 :(得分:5)

猖獗suggested模式同义词,我同意了,但无可否认的是如何正确地构建他们的签名。因此,我想我会写一个正确的答案给出完整的代码。

首先,通常的样板:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE Trustworthy #-}

module FakeFin (Nat (..), Fin (FZ, FS), FinView (..), viewFin) where
import Numeric.Natural
import Unsafe.Coerce

现在基本类型:

data Nat = Z | S Nat

-- Fin *must* be exported abstractly (or placed in an Unsafe
-- module). Users can use its constructor to implement
-- unsafeCoerce!
newtype Fin (n :: Nat) = Fin Natural
deriving instance Show (Fin n)

通过视图类型而不是直接工作要容易得多,所以让我们定义一个:

data FinView n where
  VZ :: FinView ('S n)
  VS :: !(Fin n) -> FinView ('S n)
deriving instance Show (FinView n)

重要的是要注意我们可以使用显式的等式约束定义FinView,因为我们必须在这些术语中考虑给出正确的模式签名:

data FinView n where
  VZ :: n ~ 'S m => FinView n
  VS :: n ~ 'S m => !(Fin m) -> FinView n

现在实际的视图功能:

viewFin :: Fin n -> FinView n
viewFin (Fin 0) = unsafeCoerce VZ
viewFin (Fin n) = unsafeCoerce (VS (Fin (n - 1)))

模式签名精确地反映了FinView构造函数的签名。

pattern FZ :: () => n ~ 'S m => Fin n
pattern FZ <- (viewFin -> VZ) where
  FZ = Fin 0

pattern FS :: () => n ~ 'S m => Fin m -> Fin n
pattern FS m <- (viewFin -> VS m) where
  FS (Fin m) = Fin (1 + m)

-- Let GHC know that users need only match on `FZ` and `FS`.
-- This pragma only works for GHC 8.2 (and presumably future
-- versions).
{-# COMPLETE FZ, FS #-}

为了完整性(因为我花了相当多的努力来写这个比我想象的更多),如果这个模块意外地导出unsafeCoerce数据构造函数,这里有一种写Fin的方法。我想可能有更简单的方法。

import Data.Type.Equality

type family YahF n a b where
  YahF 'Z a _ = a
  YahF _ _ b = b

newtype Yah n a b = Yah (YahF n a b)

{-# NOINLINE finZBad #-}
finZBad :: 'Z :~: n -> Fin n -> a -> b
finZBad pf q =
  case q of
    FZ -> blah (trans pf Refl)
    FS _ -> blah (trans pf Refl)
  where
    blah :: forall a b m. 'Z :~: 'S m -> a -> b
    blah pf2 a = getB pf2 (Yah a)

    {-# NOINLINE getB #-}
    getB :: n :~: 'S m -> Yah n a b -> b
    getB Refl (Yah b) = b

myUnsafeCoerce :: a -> b
myUnsafeCoerce = finZBad Refl (Fin 0)

finZBad是所有动作发生的地方,但它没有做任何远程不正确的事情!如果有人真的给我们一个类型为Fin'Z的非底价值,那么事情就已经非常糟糕了。这里的显式类型相等证据是必要的,因为如果GHC看到代码需要'Z ~ 'S m,它将简单地拒绝它; GHC并不喜欢约束中的假设推理。 NOINLINE注释是必要的,因为GHC的简化器本身使用类型信息;处理它所知道的事情的证据是不可能的,它非常混乱,结果非常随意。所以我们阻止它并成功实现邪恶功能。