关于haskell:是否有一种快速算法来确定上下文无关语言的术语的godel数?

Is there a fast algorithm to determine the godel number of a term of a context free language?

假设我们有一个简单的语法规范。有一种枚举该语法项的方法,该方法通过对角迭代来确保任何有限项都具有有限位置。例如,对于以下语法:

1
2
3
4
5
6
S      ::= add
add    ::= mul | add + mul
mul    ::= term | mul * term
term   ::= number | ( S )
number ::= digit | digit number
digit  ::= 0 | 1 | ... | 9

您可以列举如下术语:

1
2
3
4
5
6
7
8
9
10
11
0
1
0+0
0*0
0+1
(0)
1+0
0*1
0+0*0
00
... etc

我的问题是:有没有办法做相反的事情?也就是说,采用该语法的有效术语,例如0+0*0,并找到其在这种枚举中的位置-在这种情况下,为9?


对于这个特定的问题,如果我们允许自己选择不同的枚举顺序,那么我们可以做一些相当简单的事情。这个想法基本上是"每一位计数"中的一个,我也在评论中提到了这一点。首先,进行一些准备:一些导入/扩展,表示语法的数据类型以及漂亮的打印机。为了简单起见,我的位数仅增加到2(足够大,不再可以使用二进制数,但是又足够小,不会磨损我的手指和眼睛)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{-# LANGUAGE TypeSynonymInstances #-}
import Control.Applicative
import Data.Universe.Helpers

type S      = Add
data Add    = Mul    Mul    | Add :+ Mul       deriving (Eq, Ord, Show, Read)
data Mul    = Term   Term   | Mul :* Term      deriving (Eq, Ord, Show, Read)
data Term   = Number Number | Parentheses S    deriving (Eq, Ord, Show, Read)
data Number = Digit  Digit  | Digit ::: Number deriving (Eq, Ord, Show, Read)
data Digit  = D0 | D1 | D2                     deriving (Eq, Ord, Show, Read, Bounded, Enum)

class PP a where pp :: a -> String
instance PP Add where
    pp (Mul m) = pp m
    pp (a :+ m) = pp a ++"+" ++ pp m
instance PP Mul where
    pp (Term t) = pp t
    pp (m :* t) = pp m ++"*" ++ pp t
instance PP Term where
    pp (Number n) = pp n
    pp (Parentheses s) ="(" ++ pp s ++")"
instance PP Number where
    pp (Digit d) = pp d
    pp (d ::: n) = pp d ++ pp n
instance PP Digit where pp = show . fromEnum

现在让我们定义枚举顺序。我们将使用两个基本的组合器,+++用于交织两个列表(助记符:中间字符是一个和,因此我们从第一个参数或第二个参数中获取元素)和+*+进行对角化(助记符:中间字符是一个产品,因此我们从第一个和第二个参数中获取元素)。有关这些信息的更多信息,请参见Universe文档。我们将保持不变的一个方面是,我们的列表(digits除外)始终是无限的。稍后将很重要。

1
2
3
4
5
6
ss    = adds
adds  = (Mul    <$> muls   ) +++ (uncurry (:+) <$> adds +*+ muls)
muls  = (Term   <$> terms  ) +++ (uncurry (:*) <$> muls +*+ terms)
terms = (Number <$> numbers) +++ (Parentheses <$> ss)
numbers = (Digit <$> digits) ++ interleave [[d ::: n | n <- numbers] | d <- digits]
digits  = [D0, D1, D2]

让我们看看一些术语:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
*Main> mapM_ (putStrLn . pp) (take 15 ss)
0
0+0
0*0
0+0*0
(0)
0+0+0
0*(0)
0+(0)
1
0+0+0*0
0*0*0
0*0+0
(0+0)
0+0*(0)
0*1

好的,现在让我们开始吧。假设我们有两个无限列表ab。有两件事要注意。首先,在a +++ b中,所有偶数索引均来自a,所有奇数索引均来自b。因此,我们可以查看索引的最后一位以查看要查找的列表,并选择其余的位来选择该列表中的索引。其次,在a +*+ b中,我们可以使用数字对和单个数字之间的标准双射来在大列表中的索引与ab列表中的索引对之间进行转换。好的!让我们开始吧。我们将为可哥德尔的事物定义一个类,该类可以在数字之间来回转换-索引到无限的居民列表中。稍后,我们将检查此翻译是否与我们上面定义的枚举匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Nat = Integer -- bear with me here
class Godel a where
    to :: a -> Nat
    from :: Nat -> a

instance Godel Nat where to = id; from = id

instance (Godel a, Godel b) => Godel (a, b) where
    to (m_, n_) = (m + n) * (m + n + 1) `quot` 2 + m where
        m = to m_
        n = to n_
    from p = (from m, from n) where
        isqrt    = floor . sqrt . fromIntegral
        base     = (isqrt (1 + 8 * p) - 1) `quot` 2
        triangle = base * (base + 1) `quot` 2
        m = p - triangle
        n = base - m

这里的对的实例是标准的Cantor对角线。只是一些代数:使用三角形数字来确定您要去哪里/从哪里来。现在为此类创建实例很容易。 Number仅以基数3表示:

1
2
3
4
5
6
7
8
9
10
11
12
-- this instance is a lie! there aren't infinitely many Digits
-- but we'll be careful about how we use it
instance Godel Digit where
    to = fromIntegral . fromEnum
    from = toEnum . fromIntegral

instance Godel Number where
    to (Digit d) = to d
    to (d ::: n) = 3 + to d + 3 * to n
    from n
        | n < 3     = Digit (from n)
        | otherwise = let (q, r) = quotRem (n-3) 3 in from r ::: from q

对于其余的三种类型,我们将按照上面的建议检查标记位,以确定要发出的构造函数,并将其余的位用作对角线列表的索引。所有这三个实例看起来都非常相似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
instance Godel Term where
    to (Number n) = 2 * to n
    to (Parentheses s) = 1 + 2 * to s
    from n = case quotRem n 2 of
        (q, 0) -> Number (from q)
        (q, 1) -> Parentheses (from q)

instance Godel Mul where
    to (Term t) = 2 * to t
    to (m :* t) = 1 + 2 * to (m, t)
    from n = case quotRem n 2 of
        (q, 0) -> Term (from q)
        (q, 1) -> uncurry (:*) (from q)

instance Godel Add where
    to (Mul m) = 2 * to m
    to (m :+ t) = 1 + 2 * to (m, t)
    from n = case quotRem n 2 of
        (q, 0) -> Mul (from q)
        (q, 1) -> uncurry (:+) (from q)

就是这样!现在,我们可以在语法分析树及其Godel编号之间"高效"转换此语法。此外,此翻译与上面的枚举匹配,您可以验证:

1
2
*Main> map from [0..29] == take 30 ss
True

我们确实滥用了该特定语法的许多不错的特性-非歧义性,几乎所有非终结词都有无限多个派生这一事实-但是这种技术的变化可以使您走得很远,尤其是如果您不太严格的话要求每个数字都必须与唯一的事物相关联。

另外,顺便说一句,您可能会注意到,除了(Nat, Nat)的实例外,这些Godel编号特别好,因为它们一次只看/产生一位(或三位)。因此,您可以想象进行一些流式传输。但是(Nat, Nat)太讨厌了:您必须提前知道整数才能计算sqrt。您实际上也可以将其变成流媒体家伙,而不会失去密集的特性(每个Nat与唯一的(Nat, Nat)相关联),但这是另一个答案的主题...