我试图以纯函数的方式解决这个问题,而不使用set!
。
我已经编写了一个函数,可以永久地为斐波那契数列中的每个数字调用给定的lambda。
(define (each-fib fn)
(letrec
((next (lambda (a b)
(fn a)
(next b (+ a b)))))
(next 0 1)))
我认为这很简洁,但如果我可以缩短它,请赐教:)
使用上面的定义,是否可以编写另一个函数,该函数从斐波那契数列中获取第一个n
数字并返回给我一个列表,但没有使用变量变异跟踪状态(我理解的不是真正的功能)。
函数签名不需要与以下内容相同......任何使用each-fib
而不使用set!
的方法都可以。
(take-n-fibs 7) ; (0 1 1 2 3 5 8)
我猜测有一些延续+我可以使用的currying技巧,但我一直想回到想要使用set!
,这是我想要避免的(纯粹用于学习目的/转移)我的想法纯粹是功能性的。)
答案 0 :(得分:3)
尝试使用惰性代码通过delayed evaluation:
实现(define (each-fib fn)
(letrec
((next (lambda (a b)
(fn a)
(delay (next b (+ a b))))))
(next 0 1)))
(define (take-n-fibs n fn)
(let loop ((i n)
(promise (each-fib fn)))
(when (positive? i)
(loop (sub1 i) (force promise)))))
如上所述,使用named let
可以进一步简化each-fib
:
(define (each-fib fn)
(let next ((a 0) (b 1))
(fn a)
(delay (next b (+ a b)))))
无论哪种方式,都需要稍微修改each-fib
以使用delay
原语,这会创建一个承诺:
promise 通过
force
封装要按需评估的表达式。在承诺为force
d之后,承诺的每个后期力量都会产生相同的结果。
我想不出一种方法来阻止原始(未修改)过程无限期地迭代。但是,通过上述更改,take-n-fibs
可以根据需要继续强制对尽可能多的值进行延迟评估,而不再需要。{/ p>
此外,take-n-fibs
现在接收一个打印或依次处理每个值的功能,使用它如下:
(take-n-fibs 10 (lambda (n) (printf "~a " n)))
> 0 1 1 2 3 5 8 13 21 34 55
答案 1 :(得分:1)
您提供了斐波那契元素的迭代函数。如果你想要,而不是迭代每个元素,累积结果,你应该使用一个不同的原语fold
(或reduce
)而不是{ {1}}。
(可能可以使用continuation将iter
转换为iter
,但使用{{}的直接解决方案可能会降低可读性并降低效率1}}或变异。)
但请注意,使用通过变异更新的累加器也没问题,只要您了解自己在做什么:为方便起见,您在本地使用可变状态,但从外部看,函数fold
是观察性纯粹,所以你不要“污染”你的程序作为一个整体有副作用。
fold
的快速原型,改编自您自己的代码。我对“何时停止折叠”做出了任意选择:如果函数返回take-n-fibs
,我们返回当前累加器而不是继续折叠。
fold-fib
最好有一个更强大的约定来结束折叠。
答案 2 :(得分:1)
我写了几个变种。首先你问是否
(define (each-fib fn)
(letrec
((next (lambda (a b)
(fn a)
(next b (+ a b)))))
(next 0 1)))
可以写得更短。该模式经常被使用,因此引入了称为named 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)
现在手动编写风格有点烦人,所以可以
方便介绍合作例程。打破不使用set!
的规则我选择使用共享变量fibs
,其中generate-fibs
重复地将新的斐波那契数字包含在其中。当倒计时结束时,count-down
例程只读取值。
(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特定的,其他版本应该在任何地方运行。
答案 3 :(得分:0)
建立一个列表很难。但显示结果仍然可以完成(以非常糟糕的方式)
#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