如何在Scheme中更改值,但仅使用纯功能范例

时间:2019-06-13 03:19:03

标签: variables functional-programming scheme lisp racket

我正在尝试更改某些变量的值,但是如果不离开功能范例(例如使用set!),我将无法更改。有什么办法可以做到吗?

示例代码:

(lambda ()
      (let ((more a))
        (set! a b)
        (set! b (+ more b))

我想使用a的值来更改b,我想使用b的值来更改(+ more b),但是要使用纯功能范式,而没有set!

4 个答案:

答案 0 :(得分:6)

您不能执行此操作,但是可以执行等效操作。假设我有一些函数f1,其中绑定了ab(假设它们是参数,因为这会使事情变得更容易)。在某些时候,我想交换ab。所以我从这个开始:

(define f1
  (λ (a b)
    ... code that uses a and b ...
    (let ([tmp a])
      (set! a b)
      (set! b tmp))
    ... code that uses a and b, now swapped ...))

此代码显然不起作用,因为其中具有赋值。但我可以这样做,发明一个新功能f2

(define f1
  (λ (a b)
    ... code that uses a and b ...
    (f2 b a)))

(define f2
  (λ (a b)
    ... code that uses a and b, now swapped ...))

此代码功能,并且执行相同的操作。然后我就可以摆脱f2的名字了,因为函数是一流的:

(define f1
  (λ (a b)
    ... code that uses a and b ...
    ((λ (a b)
       ... code that uses a and b, now swapped ...)
     b a))

(显然,我们将其写为:

(define f1
  (λ (a b)
    ...
    (let ([b a] [a b])
      ...)))

是同一回事。)

因此,此代码现在的功能与原始代码完全相同,不同之处在于它纯粹是功能性的(嗯:只要省略号中的代码可以(可能不是),因为第一个块实际上只能做某事)副作用)。

现在这是一个聪明的地方:该方案必须正确地进行尾递归。这意味着必须通过实现消除尾调用。在Scheme中,函数调用本质上是GO TO传递自变量,正如著名的“ Lambda the Ultimate”论文之一Debunking the 'Expensive Procedure Call' Myth, or, Procedure Call Implementations Considered Harmful, or, Lambda: The Ultimate GOTO中所述。最初为f2并成为匿名函数的函数调用是尾部调用,因此必须消除。任何合理的Scheme实现都很可能会将这段代码转换为与具有赋值的朴素代码相同或更好的代码。


关于lambda-the-ultimate论文的注释:用来存放它们的副本的站点以及仍然有很多链接的站点(包括Wikipedia的)已经变成了垃圾邮件:请点击这些链接(该站点的名称包含“ read”和“ scheme”一词)。现在寻找它们的最佳地点似乎是AI Memos repository at MIT。令人讨厌的是,它们变得非常难找到,因为它们绝对是基础论文。

答案 1 :(得分:4)

修改变量的那一刻,您将退出功能范式。最后标有“ !”的过程本质上是 procedural ,如果要编写纯功能代码,则应避免使用它们。

您可以 做的是调用另一个函数(如果写一个循环,可能是同一个函数),将“变量”的 new 值作为参数传递。例如:

(define a 26)
(define b 16)

(define (print-new-values a b)
  ; the modified values exist
  ; only inside this procedure
  (printf "a is ~s~n" a)
  (printf "b is ~s~n" b))

(let ((more a))
  ; notice that the parameter a = b
  ; and that the parameter b = more + b
  ; we didn't reassign anything, instead
  ; the parameters got bound to new values  
  (print-new-values b (+ more b)))

=> a is 16
   b is 42

上面的代码具有与您打算编写的代码完全相同的输出,但是没有使用set!。为了进行比较:

(define a 26)
(define b 16)

(let ((more a))
  (set! a b)
  (set! b (+ more b))
  (printf "a is ~s~n" a)
  (printf "b is ~s~n" b))

=> a is 16
   b is 42

答案 2 :(得分:1)

它可以与阴影一起使用。例如。

(let ((a 10)) (b 20))
  (let ((b a) (a b))
    (list a b))) ; ==> (20 10)

但是,如果您想拥有充分的灵活性,可以使用对象:

(define (fib-node a b)
  (cons a b))

(define (fib-b f)
  (cdr f))

(define (fib-value f)
  (car f))

(define (fib-next f)
  (let ((b (fib-b f)))
    (fib-node b (+ b (fib-value f)))))

(define fib-null (fib-node 0 1))

;; iterating Fibonacci numbers without setting anything
(let loop ((n 10) (cur fib-null) (acc '()))
  (if (zero? n)
      (reverse acc)
      (loop (- n 1) (fib-next cur) (cons (fib-value cur) acc))))
; ==> (0 1 1 2 3 5 8 13 21 34)

答案 3 :(得分:0)

有很多方法可以做到这一点,最著名的是monad。但这是一种称为延续传递样式的方法:

(define set/a/b
  (lambda (a b return)
    ;; new/a <= b
    ;; new/b <= a+b
    (return b (+ a b))))

(define new/values
  (lambda (a b return)
    (set/a/b a b return)))

(new/values 10 20
  (lambda (new/a new/b)
    (display new/a)(newline)
    (display new/b)(newline)))