Functional programming 无限斐波那契级数,从列表中只取n,不使用变异?

Functional programming 无限斐波那契级数,从列表中只取n,不使用变异?,functional-programming,scheme,fibonacci,Functional Programming,Scheme,Fibonacci,我试图用纯函数的方式解决这个问题,而不使用set 我写了一个函数,它为斐波那契级数中的每个数字调用给定的lambda,永远 (define (each-fib fn) (letrec ((next (lambda (a b) (fn a) (next b (+ a b))))) (next 0 1))) 我认为这是尽可能简洁的,但如果我能缩短它,请启发我:) 使用上述定义,是否可以编写另一个函数,从斐波那契数列中提取第一个

我试图用纯函数的方式解决这个问题,而不使用
set

我写了一个函数,它为斐波那契级数中的每个数字调用给定的lambda,永远

(define (each-fib fn)
  (letrec
    ((next (lambda (a b)
             (fn a)
             (next b (+ a b)))))
    (next 0 1)))
我认为这是尽可能简洁的,但如果我能缩短它,请启发我:)

使用上述定义,是否可以编写另一个函数,从斐波那契数列中提取第一个
n
数字,并返回一个列表,但不使用变量变异来跟踪状态(我知道这不是真正的函数)

函数签名不需要与以下内容相同。。。任何使用
每个fib而不使用
集合的方法很好

(take-n-fibs 7) ; (0 1 1 2 3 5 8)

我猜我可以使用某种延续+咖喱技巧,但我一直想使用
set,这正是我试图避免的(纯粹出于学习目的/将我的思维转向纯粹的功能性)。

您提供了斐波那契元素上的迭代函数。如果您不想迭代每个元素来累积结果,您应该使用一个不同的原语,它是一个
折叠
(或
减少
)而不是
iter

(可能可以使用延续将
iter
转换为
折叠
,但与使用
折叠
或变异的直接解决方案相比,这可能更难阅读,效率更低。)

然而,请注意,使用由突变更新的累加器也是可以的,只要您了解自己在做什么:您在本地使用可变状态是为了方便,但是从外部看,函数
take-n-fibs
在观察上是纯的,因此您不会因副作用而“污染”整个程序

根据您自己的代码改编的
折叠fib
快速原型。我任意选择了“何时停止折叠”:如果函数返回
null
,我们将返回当前累加器,而不是继续折叠

(define (fold-fib init fn) (letrec ([next (lambda (acc a b)
      (let ([acc2 (fn acc a)])
        (if (null? acc2) acc
            (next acc2 b (+ a b)))))])
      (next init 0 1)))

(reverse (fold-fib '() (lambda (acc n) (if (> n 10) null (cons n acc)))))

最好有一个更强大的约定来结束折叠。

我写了几个变体。首先你要问

(define (each-fib fn)
  (letrec
    ((next (lambda (a b)
             (fn a)
             (next b (+ a b)))))
    (next 0 1)))
可以写得更短。该模式经常使用,因此引入了称为
命名let
的特殊语法。使用命名let时,您的函数如下所示:

(define (each-fib fn)
  (let next ([a 0] [b 1])
    (fn a)
    (next b (+ a b))))
为了使控件从一个函数流向另一个函数,可以在支持TCO的语言中使用延续传递样式。每个函数都有一个额外的参数,通常称为k(用于继续)。函数k表示下一步要做什么

使用此样式,可以按如下方式编写程序:

(define (generate-fibs k)
  (let next ([a 0] [b 1] [k k])
    (k a (lambda (k1) 
           (next b (+ a b) k1)))))

(define (count-down n k)
  (let loop ([n n] [fibs '()] [next generate-fibs])
    (if (zero? n)
        (k fibs)
        (next (λ (a next)
                (loop (- n 1) (cons a fibs) next))))))

(count-down 5 values)
现在手动编写样式有点烦人,所以它可以 方便地介绍co例程。打破你不使用
set的规则
我选择使用一个共享变量
fibs
,其中
生成fibs
重复使用新的fibonacci数。当倒计时结束时,
倒计时
例程仅读取值

(define (make-coroutine co-body)
  (letrec ([state (lambda () (co-body resume))]
           [resume (lambda (other)
                     (call/cc (lambda (here)
                                (set! state here)
                                (other))))])
    (lambda ()
      (state))))

(define fibs '())

(define generate-fib
  (make-coroutine 
   (lambda (resume)
     (let next ([a 0] [b 1])
       (set! fibs (cons a fibs))
       (resume count-down)
       (next b (+ a b))))))

(define count-down
  (make-coroutine   
   (lambda (resume)
     (let loop ([n 10])
       (if (zero? n)
           fibs
           (begin
             (resume generate-fib)
             (loop (- n 1))))))))

(count-down)     
另外,您还可以获得一个具有通信线程的版本:

#lang racket
(letrec ([result #f]
         [count-down 
          (thread 
           (λ ()
             (let loop ([n 10] [fibs '()])
               (if (zero? n)
                   (set! result fibs)
                   (loop (- n 1) (cons (thread-receive) fibs))))))] 

         [produce-fibs
          (thread
           (λ ()
             (let next ([a 0] [b 1])
               (when (thread-running? count-down)
                 (thread-send count-down a)
                 (next b (+ a b))))))])
  (thread-wait count-down)
  result)

线程版本是特定于Racket的,其他版本应该在任何地方运行。

试试这个,使用惰性代码通过以下方式实现:

如前所述,
每个fib
都可以通过使用:

无论采用哪种方式,都有必要对每个fib进行一点修改,以使用
延迟
原语,它创建了一个承诺:

承诺封装了通过
force
按需计算的表达式。在允诺被强制执行后,允诺的每一次后续强制都会产生相同的结果

我想不出一种方法来阻止原始(未修改的)过程无限期地迭代。但是,有了上述更改,
take-n-fibs
可以继续强制执行所需的任意多个值的延迟计算,而不再需要

另外,
take-n-fibs
现在接收到一个函数,用于依次打印或处理每个值,如下所示:

(take-n-fibs 10 (lambda (n) (printf "~a " n)))
> 0 1 1 2 3 5 8 13 21 34 55

建立一份清单是很困难的。但是仍然可以显示结果(以非常糟糕的方式)


我越想这一点,我就越开始意识到,如果不改变
每个fib
的定义来计算,这是不可能的。事实上
每个fib
都必须以某种方式改变,但是
设置可以避免(意思是:不需要显式变异)
(define (each-fib fn)
  (let next ((a 0) (b 1))
    (fn a)
    (delay (next b (+ a b)))))
(take-n-fibs 10 (lambda (n) (printf "~a " n)))
> 0 1 1 2 3 5 8 13 21 34 55
#lang racket

(define (each-fib fn)
  (letrec
    ((next (lambda (a b)
             (fn a)
             (next b (+ a b)))))
    (next 0 1)))



(define (take-n-fibs n fn)
  (let/cc k
        (begin
          (each-fib (lambda (x) 
                      (if (= x (fib (+ n 1)))
                          (k (void))
                          (begin
                            (display (fn x))
                            (newline))))))))


(define fib
  (lambda (n)
    (letrec ((f
              (lambda (i a b)
                (if (<= n i)
                    a
                    (f (+ i 1) b (+ a b))))))
      (f 1 0 1))))
(take-n-fibs 7 (lambda (x) (* x x))) 
0
1
1
4
9
25
64