原始递归函数的指称语义表示

时间:2014-11-30 16:04:17

标签: haskell

有没有办法在Haskell中表示原始递归函数(PRF)的指称语义?

1 个答案:

答案 0 :(得分:5)

排序-的。我们可以使用Haskell类或GADT对原始递归函数进行编码。然后我们可以认为原始递归函数是数据类型的等价类。最简单的等价是关于PRF解释的Haskell外延语义。由于Haskell的指称语义,这种表示最终将是不精确的,但让我们探讨我们能够接近的程度。

原始递归函数

我们将使用definition of primitive recursive functions from WikipediaPRF a是具有arity a的原始递归函数,其中a是自然数。

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE GADTs #-}

data PRF (a :: Nat) where
    Const ::                             PRF 'Z
    Succ  ::                             PRF ('S 'Z)
    Proj  :: BNat n                   -> PRF n
    Comp  :: PRF k  -> List k (PRF m) -> PRF m
    PRec  :: PRF k  -> PRF (S (S k))  -> PRF (S k)

Const构造常量函数或arity零,它始终返回0. Succ是arity one的后继函数。 Proj构造了一系列投影函数,每个函数在跳过提供的参数数量后都会选出一个参数。 Comp用一个提供其参数的其他函数列表组成一个函数。 PRec构建一个模式匹配第一个参数的函数。如果第一个参数为零,则PRec将第一个函数应用于其余参数。如果第一个参数不为零,它将第一个参数的前一个作为第一个参数递归到自身,并返回应用于第一个参数的前一个的第二个函数的结果,递归的结果,以及其余的参数。从PRF到Haskell函数的编译器定义中更容易看到。

compile :: PRF n -> List n Nat -> Nat
compile Const = const Z
compile Succ  = \(Cons n Nil) -> S n
compile (Proj n) = go n
    where
        go :: BNat n -> List n a -> a
        go BZero     (Cons h _) = h
        go (BSucc n) (Cons _ t) = go n t
compile (Comp f gs) = \ns -> f' . fmap ($ ns) $ gs'
    where
        gs' = fmap compile gs
        f'  = compile f
compile (PRec f g) = h
    where
        h (Cons Z t)     = f' t
        h (Cons (S n) t) = g' (Cons n (Cons (h (Cons n t)) t))
        f' = compile f
        g' = compile g

以上要求自然数Nat的定义,由类型级自然数BNat限定的自然数,以及类型级已知长度{{1 }}

List

我们现在有能力编写我们的第一个原始递归函数。我们将编写两个用于标识和添加的示例。

import qualified Data.Foldable as Foldable
import System.IO

data Nat = Z | S Nat
    deriving (Eq, Show, Read, Ord)

data List (n :: Nat) a where
    Nil   ::                  List 'Z     a
    Cons  :: a -> List n a -> List ('S n) a

instance Functor (List n) where
    fmap f Nil        = Nil
    fmap f (Cons h t) = Cons (f h) (fmap f t)    

-- A natural number in the range [0, n-1]
data BNat (n :: Nat)  where
    BZero ::           BNat ('S n)
    BSucc :: BNat n -> BNat ('S n)

请注意,我们在Haskell中重用了声明来简化这些函数的编写;我们在ident :: PRF (S Z) ident = Proj BZero add :: PRF (S (S Z)) add = PRec ident (Comp Succ (Cons (Proj (BSucc BZero)) Nil)) 的定义中重复使用了ident。最终,使用Haskell声明的能力将允许我们创建无限或非完整的递归结构,我们可以潜入add类型。

我们可以编写一些示例代码来试用我们的PRF函数。我们对addseq的评估顺序有点偏执,以便我们可以看到我们的表示在以后有多么错误。

hFlush

如果我们使用mseq :: Monad m => a -> m a mseq a = a `seq` return a runPRF :: PRF n -> List n Nat -> IO () runPRF f i = do putStrLn "Compiling function" hFlush stdout f' <- mseq $ compile f putStrLn "Running function" hFlush stdout n <- mseq $ f' i print n 运行示例,我们会得到一个很好的,令人满意的输出

add

Haskell声明

我们可以使用Haskell声明做一些有趣且最终具有破坏性的事情。首先,我们将使模式匹配更容易。能够使用runPRF add (Cons (S (S Z)) (Cons (S (S (S Z))) Nil)) Compiling function Running function S (S (S (S (S Z)))) 中的模式匹配而不提供使用递归结果的函数是很好的。 PRec将为我们添加额外的伪参数。

match

要做到这一点,它需要一个辅助函数,它添加参数match :: (Depths List k) => PRF k -> PRF (S k) -> PRF (S k) match fz fs = PRec fz (addArgument (BSucc BZero) fs) 和一些其他实用程序来测量具有已知类型的列表的大小addArgument,比较和转换{{ 1}} s,并证明递增的自然数仍然在新的界限之内。

Depths

在编写完全合理的内容(例如

)时,这非常有用
BNat

递归Haskell声明

我们还可以编写非常具有破坏性的事情,而不仅仅是{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE StandaloneDeriving #-} class Depths f (n :: Nat) where depths :: f n (BNat n) instance Depths List 'Z where depths = Nil instance (Depths List n) => Depths List ('S n) where depths = Cons BZero (fmap BSucc depths) deriving instance Eq (BNat n) deriving instance Show (BNat n) deriving instance Ord (BNat n) bid :: BNat n -> BNat (S n) bid BZero = BZero bid (BSucc x) = BSucc (bid x) addArgument :: (Depths List k) => BNat (S k) -> PRF k -> PRF (S k) addArgument n f = Comp f . fmap p $ depths where p d = if d' >= n then Proj (BSucc d) else Proj d' where d' = bid d 。首先,我们使用递归定义{{​​1}}构造。我们知道用nonZero :: PRF (S Z) nonZero = match Const (Comp Succ (Cons (Comp Const Nil) Nil)) isZero :: PRF (S Z) isZero = match (Comp Succ (Cons Const Nil)) (Comp Const Nil) isOdd :: PRF (S Z) isOdd = PRec Const (addArgument BZero isZero) 构建的东西不应该存在于原始递归函数的闭包中。

undefined

这使我们可以编写一个仅针对某些输入进行非终止的循环。

while

如果我们为偶数(例如whilewhile :: (Depths List k) => PRF (S k) -> PRF (S k) -> PRF (S k) while test step = goTest where --goTest :: PRF (S k) goTest = Comp goMatch (Cons test (fmap Proj depths)) --goMatch :: PRF (S (S k)) goMatch = match (Proj BZero) (addArgument BZero goStep) --goStep :: PRF (S k) goStep = Comp goTest (Cons step (fmap (Proj . BSucc) depths)) )运行此操作,则会终止返回输入。如果我们以奇数运行它,它永远不会完成。

infiniteLoop :: PRF (S Z)
infiniteLoop = while isOdd (Comp Succ (Cons Succ Nil))

因为我们对ZS (S Z)小心谨慎,所以我们可以确定编译后的值是以星期正常形式存在的,这种形式不是原始的递归函数,并且不是#39}。 ; t只是runPRF infiniteLoop (Cons (S Z) Nil) Compiling function Running function 。这是因为seq步骤并不严格,减少到星期正常形式并没有导致减少一直到正常形式。我们可以通过将hFlush添加到undefined来解决此问题。我只改变了需要它的两种模式。

compile

这基本上会在编译时检查seq是否有限。

compile

整理

我们所讨论的所有类型都没有真正代表一对一的原始递归函数。 compile (Comp f gs) = f' `seq` gs' `seq` go where go = \ns -> f' . fmap ($ ns) $ gs' gs' = fmap compile gs f' = compile f compile (PRec f g) = f' `seq` g' `seq` h where h (Cons Z t) = f' t h (Cons (S n) t) = g' (Cons n (Cons (h (Cons n t)) t)) f' = compile f g' = compile g 除了上面定义的递归结构和PRF之外还有其他内容。它也存在于相同原始递归函数的多个表示中。例如,身份函数具有其他定义,包括具有后继函数的前趋函数(我没有定义)的组成。编译的结果runPRF infiniteLoop (Cons Z Nil) Compiling function GHC stack-space overflow: current limit is 33632 bytes. Use the `-K<size>' option to increase it. 由任何具有相同类型的Haskell函数居住,其中也包括所有部分递归函数。

为了隐藏同一个函数的多个表示,我们可以使用与Haskell相同的技巧:隐藏函数的内部。如果有人可以检查PRF a的唯一方法是严格编译它并将其应用于某些东西,那么没有人能够区分出相同的原始递归函数之间的区别。

将我们的GADT转换为类型类,只导出类和undefined就足以隐藏构造函数。

如果我们稍稍扭曲我们的头,并注意到原始递归函数的公理就像List n Nat -> Nat定律,PRF没有compile,可以找到要导出的

Another interface (实际上它与Category相反),以及仅适用于自然数的有限形式的循环。

这足以让你相信这几乎是可能的。无论我们做什么,我们仍然会被额外的居民所困扰,Arrow。关于how to make it nice would belong to a different question的进一步讨论,包括对它应该如何好的具体问题。