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