是否可以在 lambda 演算上实现 returns 一个 n 元组的函数?

Is it possible to implement a function that returns an n-tuple on the lambda calculus?

lambda 演算上的 n 元组通常定义为:

1-tuple:     λ a t . t a
1-tuple-fst: λ t . t (λ a . a)

2-tuple:     λ a b t . t a b
2-tuple-fst: λ t . t (λ a b . a)
2-tuple-snd: λ t . t (λ a b . b)

3-tuple:     λ a b c t . t a b c
3-tuple-fst: λ t . t (λ a b c . a)
3-tuple-snd: λ t . t (λ a b c . b)
3-tuple-trd: λ t . t (λ a b c . c)

... and so on.

我的问题是:是否可以实现一个接收教堂编号 N 和 returns 任何 N 对应的 N 元组的函数?另外,是否可以扩展此功能,使其也 returns 相应的访问器? 该算法不能使用任何形式的递归,包括定点组合器。

~

编辑:根据要求,详细说明我的尝试。

我希望该函数不依赖于递归/定点组合器,因此,最明显的方法是使用教堂编号进行重复。话虽如此,我尝试过随机测试许多表情,以了解它们是如何成长的。例如:

church_4 (λ a b c . a (b c))

减少到:

(λ a b c d e f . a ((((e d) c) b) a)))))

我将许多相似组合的减少 church_4 (λ a b c . (a (b c))) 与我想要的结果进行了比较,并注意到我可以将访问器实现为:

firstOf = (λ max n . (firstOf (sub max n) (firstOf n)))
access = (λ max idx t . (t (firstOf (sub max idx) (firstOf idx))))

其中 sub 是减法运算符,access church_5 church_2 表示访问 6 元组的第 3 个(因为 2 是第 3 个自然元素)元素。

现在,关于元组。请注意,问题是找到一个术语 my_term,例如:

church_3 my_term

具有以下范式:

(λ a b c d t . ((((t a) b) c) d))

如你所见,我几乎找到它了,因为:

church_3 (λ a b c . a (b c)) (λ a . a)

减少到:

(λ a b c d . (((a b) c) d))

这几乎就是我需要的结果,只是少了t

这就是我到目前为止所尝试的方法。

如果您可以构建 n 元组,则可以轻松访问第 i 个索引。

首先,我们需要一个类型,用于无限的无类型 lambda 函数。额外的 X 构造函数允许我们通过执行这些函数来检查它们。

import Prelude hiding (succ, pred)

data Value x = X x | F (Value x -> Value x)

instance (Show x) => Show (Value x) where
    show (X x) = "X " ++ show x
    show _     = "F"

能互相套用函数很方便

ap :: Value x -> Value x -> Value x
ap (F f) = f
ap _     = error "Attempt to apply Value"

infixl 1 `ap`

如果您要使用教堂数字对数字进行编码,则需要一些教堂数字。我们还需要减法来计算在索引到 n 元组时要跳过多少个额外参数。

idF = F $ \x -> x

zero = F $ \f -> idF
succ = F $ \n -> F $ \f -> F $ \x -> f `ap` (n `ap` f `ap` x)

one = succ `ap` zero
two = succ `ap` one
three = succ `ap` two
four = succ `ap` three

pred = F $ \n -> F $ \f -> F $ \x -> n `ap` (F $ \g -> F $ \h -> h `ap` (g `ap` f)) `ap` (F $ \u -> x) `ap` idF

subtractF = F $ \n -> (n `ap` pred)

常量函数删除它的第一个参数。如果我们将常量函数迭代一些数字次数,它会丢弃那么多第一个参数。

--drops the first argument
constF = F $ \f -> F $ \x -> f
-- drops i first arguments
constN = F $ \i -> i `ap` constF

我们可以创建另一个去掉第二个参数的常量函数。如果我们将它迭代一些数字次数,它会丢弃那么多第二个参数。

-- drops the second argument
constF' = F $ \f -> F $ \a -> F $ \b -> f `ap` a
-- drops n second arguments
constN' = F $ \n -> n `ap` constF'

要索引到 n 元组的第 i 个索引(第一个索引从 zero 开始),我们需要从末尾删除 n-i-1 个参数并且从一开始就删除 i 个参数。

-- drops (n-i-1) last arguments and i first arguments
access = F $ \n -> F $ \i -> constN `ap` i `ap` (constN' `ap` (subtractF `ap` (succ `ap` i) `ap` n) `ap` idF)

我们将定义几个固定大小的示例元组

tuple1 = F $ \a ->                     F $ \t -> t `ap` a
tuple2 = F $ \a -> F $ \b           -> F $ \t -> t `ap` a `ap` b
tuple3 = F $ \a -> F $ \b -> F $ \c -> F $ \t -> t `ap` a `ap` b `ap` c

我们可以用它来证明可以生成相应的访问器

main = do
    print $ tuple1 `ap` (X "Example")           `ap` (access `ap` one `ap` zero)

    print $ tuple2 `ap` (X "Hello") `ap` (X "World") `ap` (access `ap` two `ap` zero)
    print $ tuple2 `ap` (X "Hello") `ap` (X "World") `ap` (access `ap` two `ap` one)

    print $ tuple3 `ap` (X "Goodbye") `ap` (X "Cruel") `ap` (X "World") `ap` (access `ap` three `ap` zero)
    print $ tuple3 `ap` (X "Goodbye") `ap` (X "Cruel") `ap` (X "World") `ap` (access `ap` three `ap` one)
    print $ tuple3 `ap` (X "Goodbye") `ap` (X "Cruel") `ap` (X "World") `ap` (access `ap` three `ap` two)

运行 这输出

X "Example"
X "Hello"
X "World"
X "Goodbye"
X "Cruel"
X "World"

要构造元组,您需要迭代一些函数,将参数添加到函数而不是删除它们。

foldargs = λ t n f z . (IsZero n) (t z) (λ a . foldargs t (pred n) f (f a z))

然后函数

listofargs = λ n . foldargs id n pair null

returns 其参数的反向列表:

listofargs 5 a b c d e --> (e . (d . (c . (b . (a . null))))) or [e d c b a]

函数

apply = λ f l . (isnil l) f (apply (f (head l)) (tail l))

将第一个参数(n 元函数)应用于从第二个参数(长度为 n 的列表)中获取的参数:

apply f [a b c d e]  --> f a b c d e

剩下的很简单:

n-tuple = λ n . foldargs n-tuple' (Succ n) pair null 

哪里

n-tuple' = λ l . apply (head l) (reverse (tail l))

其他函数的实现可以参考wikipediaY-combinator可以消除递归。 reverse很简单。

UPD:函数的非递归版本:

foldargs = Y (λ c t n f z . (IsZero n) (t z) (λ a . c t (pred n) f (f a z)))
apply = Y (λ c f l . (isnil l) f (c (f (head l)) (tail l)))
Y = λ f (λ x . f x x) (λ x . f x x)

\\ 

让我们尝试实现 n 元元组构造函数。我还将以简单的实现为目标,这意味着我会尝试坚持消除自然数和元组,并尽量避免使用其他(Church 编码)数据结构。

我的策略如下:

  1. 用相关语言编写函数的类型正确的版本。
  2. 将其转换为无类型的 lambda 演算。

这样做的原因是,我很快就迷失在无类型的 lambda 演算中,并且在这个过程中我肯定会犯很多错误,而依赖类型的环境让我陷入 rails。此外,证明助手对编写任何类型的代码都非常有帮助。

第一步

我用的是 Agda。我用 type-in-type 作弊了一点。它使 Agda 不一致,但对于这个问题,正确类型的宇宙将是一个巨大的痛苦,而且我们实际上 运行 无论如何都不太可能陷入不一致。

{-# OPTIONS --type-in-type #-}

open import Data.Nat
open import Data.Vec

我们需要 n 元多态函数的概念。我们将参数类型存储在长度为 n:

的向量中
NFun : ∀ {n} → Vec Set n → Set → Set
NFun []       r = r
NFun (x ∷ ts) r = x → NFun ts r
-- for example, NFun (Nat ∷ Nat ∷ []) = λ r → Nat → Nat → r

我们有元组的常用 Church 编码。 n 元元组的构造函数是 n 元函数 returning 一个元组。

NTup : ∀ {n} → Vec Set n → Set
NTup ts = ∀ {r} → NFun ts r → r

NTupCons : ℕ → Set
NTupCons n = ∀ ts → NFun {n} ts (NTup ts)

我们想要一个类型为 ∀ {n} → NTupCons n 的函数。我们递归元组构造函数的 Vec Set n 参数。 empty 的情况很简单,但是 cons 的情况有点棘手:

nTupCons : ∀ {n} → NTupCons n
nTupCons []       x = x
nTupCons (t ∷ ts) x = ?

我们需要一个 NFun ts (NTup (t ∷ ts)) 来代替问号。我们知道 nTupCons ts 的类型是 NFun ts (NTup ts),因此我们需要以某种方式从后者获取前者。我们注意到我们需要的只是 n 元函数组合,或者换句话说,NFun:

的 return 类型的函子映射
compN : ∀ {n A B} (ts : Vec Set n) → (A → B) → NFun ts A → NFun ts B
compN []       f     = f
compN (t ∷ ts) f g x = compN ts f (g x)

现在,我们只需要从 NTup ts 中得到一个 NTup (t ∷ ts),因为我们已经有了范围内类型为 tx,这很容易:

nTupCons : ∀ {n} → NTupCons n
nTupCons []       x = x
nTupCons (t ∷ ts) x = compN ts consTup (nTupCons ts)
  where
  consTup : NTup ts → NTup (t ∷ ts)
  consTup tup con = tup (con x)

第二步

我们将去掉 Vec Set n-s 并重写函数,以便它们迭代 n 参数。然而,简单的迭代对 nTupCons 不利,因为它只为我们提供了递归结果 (nTupCons ts),但我们还需要 compN 的当前 n 索引(因为我们通过迭代 n 来实现 compN)。所以我们写了一个有点像 paramorphism 的助手。我们还需要 Church 编码对来通过迭代传递 Nat-s:

zero = λ z s. z
suc  = λ n z s. s (n z s)
fst  = λ p. p (λ a b. a)
snd  = λ p. p (λ a b. b)

-- Simple iteration has type 
-- ∀ {A} → A → (A → A) → Nat → A

-- In contrast, we may imagine rec-with-n having the following type
-- ∀ {A} → A → (A → Nat → A) → Nat → A
-- We also pass the Nat index of the hypothesis to the "cons" case

rec-with-n = λ z f n . 
  fst (
    n 
      (λ p. p z zero)
      (λ hyp p. p (f (fst hyp) (snd hyp)) (suc (snd hyp))))

-- Note: I use "hyp" for "hypothesis". 

剩下的直接翻译:

compN = λ n. n (λ f. f) (λ hyp f g x. hyp f (g x))

nTupCon = 
  rec-with-n
    (λ x. x)
    (λ hyp n. λ x. compN n (λ f g. f (g x)) hyp)

让我们测试一下简单的情况:

nTupCon zero = 
(λ t. t)

nTupCon (suc zero) =
(λ hyp n. λ x. compN n (λ f g. f (g x)) hyp) (nTupCon zero) zero =
λ x. compN zero (λ f g. f (g x)) (λ t. t) =
λ x. (λ f g. f (g x)) (λ t. t) =
λ x. λ g. (λ t. t) (g x) =
λ x . λ g. g x =
λ x g . g x

nTupCon (suc (suc zero)) =
(λ hyp n. λ x. compN n (λ f g. f (g x)) hyp) (nTupCon (suc zero)) (suc zero) =
λ x. compN (suc zero) (λ f g. f (g x)) (λ a t. t a) =
λ x a. (λ f g. f (g x)) ((λ y t. t y) a) =
λ x a. (λ f g. f (g x)) (λ t. t a) =
λ x a g. (λ t. t a) (g x) =
λ x a g. g x a

似乎有效。

我找到了!给你:

nTup = (λ n . (n (λ f t k . (f (λ e . (t (e k))))) (λ x . x) (λ x . x)))

测试:

nTup n1 → (λ (λ (0 1)))
nTup n2 → (λ (λ (λ ((0 1) 2))))
nTup n3 → (λ (λ (λ (λ (((0 1) 2) 3)))))
nTup n4 → (λ (λ (λ (λ (λ ((((0 1) 2) 3) 4))))))

等等。它向后存储元素,但我不认为我会修复它 - 它看起来更自然。挑战在于在最左边最里面的括号中获取 0。正如我所说,我可以很容易地同时获得 (0 (1 (2 (3 4))))((((4 3) 2) 1) 0),但它们不能用作元组,因为 0 是其中的元素。

谢谢大家!

编辑:我实际上已经解决了这个问题:

nTup = (λ a . (a (λ b c d . (b (λ b . (c b d)))) (λ x . x) (λ x . x)))

它保留了正确的顺序。

nTup n4 → (λ (λ (λ (λ (λ ((((0 4) 3) 2) 1))))))