是否有可能实现一个在lambda演算上返回n元组的函数?

时间:2015-03-25 13:43:46

标签: haskell lambda functional-programming 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并返回任何N的相应N元组的函数?此外,是否可以扩展此功能,以便它还返回相应的访问器? 该算法不能使用任何形式的递归,包括定点组合器。

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

我希望该函数不依赖于递归/定点组合器,因此,显而易见的方法是使用教会数字进行重复。说,我试过随机测试很多表达式,以了解它们是如何成长的。例如:

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

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

4 个答案:

答案 0 :(得分:5)

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

返回其args的反转列表:

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))

其他功能的实施可以从wikipedia获取。 可以通过Y-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)

 

答案 1 :(得分:5)

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

我的策略如下:

  1. 以依赖语言编写功能良好的函数版本。
  2. 将其翻译为无类型的lambda演算。
  3. 这样做的原因是我很快就迷失在无类型的lambda演算中,而且我一定会犯很多错误,而依赖类型的环境让我陷入困境。此外,证明助理对编写任何类型的代码都有很大的帮助。

    第一步

    我使用Agda。我跟type-in-type作了一些欺骗。这使得Agda不一致,但是对于这个问题,正确类型的Universe将是一个巨大的痛苦,而且我们实际上不太可能在这里遇到不一致。

    {-# 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
    

    我们有通常的教会编码元组。 n元元组的构造函数是返回元组的n元函数。

    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参数进行递归。空案例很简单,但缺点是有点棘手:

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

    我们需要NFun ts (NTup (t ∷ ts))来代替问号。我们知道nTupCons ts的类型为NFun ts (NTup ts),因此我们需要以某种方式从前者获得前者。我们注意到我们需要的只是n-ary函数组合,换句话说就是返回类型为NFun的函数映射:

    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 (t ∷ ts)获取NTup ts,因为我们已经在范围内拥有x类型t,这非常简单:

    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),但我们还需要n的当前compN索引(因为我们通过迭代compN来实现n。所以我们写了一个有点像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
    

    似乎有效。

答案 2 :(得分:3)

我找到了!你去了:

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))))))

等等。它将元素向后存储,但我不认为我会解决这个问题 - 它看起来更自然。挑战是在最左边的最里面的paren上获得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))))))

答案 3 :(得分:2)

如果您可以构建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"

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