Functional programming 在需要多次存储(当前秒数)时避免可变状态
我已经在球拍中组装了以下基本的秒表(刚刚开始学习,最终目标是一个波莫多罗计时器) 虽然这正是它应该做的,但我想知道如何避免可变状态。如果我遵循HTDP,这是一种可以保证可变状态的情况,但是在浏览了Wadler的“”之后,我仍然好奇没有Functional programming 在需要多次存储(当前秒数)时避免可变状态,functional-programming,racket,monads,purely-functional,Functional Programming,Racket,Monads,Purely Functional,我已经在球拍中组装了以下基本的秒表(刚刚开始学习,最终目标是一个波莫多罗计时器) 虽然这正是它应该做的,但我想知道如何避免可变状态。如果我遵循HTDP,这是一种可以保证可变状态的情况,但是在浏览了Wadler的“”之后,我仍然好奇没有集我怎么办 我知道,要使其功能化,我应该在函数中添加参数。例如,start将成为 (define (start [now (current-seconds)]) now) 类似的方法可以用于lap和stop 不过,尽管我知道在添加额外参数以恢复功能后,我还应该
集我怎么办代码>
我知道,要使其功能化,我应该在函数中添加参数。例如,start
将成为
(define (start [now (current-seconds)])
now)
类似的方法可以用于lap
和stop
不过,尽管我知道在添加额外参数以恢复功能后,我还应该传递参数,而不是将值存储在变量中,但我不知道在这种情况下如何利用它来避免set代码>也是
更新:由于以下三个答案都非常有价值(谢谢!),我没有将其中任何一个标记为唯一正确的答案。下面是我最初问题的最低解决方案。它结合了@Metaxal的循环建议和@Greg Hendershott的示例用法
#lang racket
(define (run)
(displayln "Enter 'lap' or 'quit':")
(let loop ([t0 (current-seconds)] [times '()])
(match (read-line)
["quit" (reverse
(map (lambda (x)
(let ((the-date (seconds->date x)))
(list
(sub1(date-hour the-date))
(date-minute the-date)
(date-second the-date)))) times))]
["lap" (loop t0 (cons (- (current-seconds) t0) times))]
[_ (loop t0 times)])))
在这种情况下,使用set
是合理的,很难避免,因为我们必须在过程调用之间“记住”状态。我们可以做的是改进状态的封装,方法是隐藏过程中更改的变量,并使用消息分派器访问引用可变状态的过程。这与我们使用面向对象编程所做的非常相似,但实现它只需要lambda
s
(define (make-timer)
; the "attributes" of the object
(let ([start-time 0]
[end-times '()])
; the "methods" of the object
(define (start)
(set! start-time (current-seconds)))
(define (lap)
(set! end-times (append end-times (list (current-seconds)))))
(define (stop)
(lap)
(display
(map (lambda (an-end)
(let ((the-date (seconds->date (- an-end start-time))))
(list
(sub1 (date-hour the-date))
(date-minute the-date)
(date-second the-date))))
end-times))
(set! end-times '()))
; return a dispatch procedure
(lambda (msg)
(case msg
((start) (start)) ; call the start procedure defined above
((lap) (lap)) ; call the lap procedure defined above
((stop) (stop)) ; call the stop procedure defined above
(else (error "unknown message:" msg))))))
我冒昧地修改了你的一些程序,使它们更简单一些。下面是我们如何使用刚刚创建的计时器对象:
(define timer (make-timer))
(timer 'start)
(sleep 1)
(timer 'lap)
(sleep 1)
(timer 'lap)
(sleep 1)
(timer 'lap)
(sleep 1)
(timer 'stop)
=> ((18 0 1) (18 0 2) (18 0 3) (18 0 4))
这项技术被称为“消息传递”,请在这本精彩的书中进一步了解它。在接下来的程序中可能会出现的情况是,您将有一个循环。
然后,这个循环可以是一个函数,它将整个当前状态作为输入,当您想要更新它的状态时,只需使用新状态再次调用该循环(当然,您也可以使用相同的确切状态再次调用该循环)
简化示例:
(define (loop [t0 (current-seconds)] [times '()])
;; ... do things here, possibly depending on user input ...
;; then loop with a new state:
(cond [<some-start-condition> (loop (current-seconds) '())]
[<some-lap-condition> (loop t0 (cons (- (current-seconds) t0) times))]
[<some-stop-condition> times])) ; stop, no loop, return value
(定义(循环[t0(当前秒)][次数'())
;;;…在这里做事情,可能取决于用户的输入。。。
;然后使用新状态循环:
(cond[(循环(当前秒))]
[(循环t0(cons(-(当前秒)t0)次))]
[时报]);停止,无循环,返回值
这当然会改变您的设计方法
在设计GUI程序时使用这种方法比较困难,因为事件循环通常会阻止(或使其难以)将值从一个事件传递到下一个事件。
然而,在球拍中,有(教学法,但仍然很好)就是为了这个而设计的。对于这样一个简单的例子,我可能会做@Metaxal
建议
但是,另一种方法是可以显式定义状态
作为结构体
:
(struct state (start-time end-times))
然后将start
、lap
和stop
更改为状态下的功能
:
;; start : -> state
;; stores start-time
(define (start)
(state (current-seconds) '()))
;; lap : state -> state
;; stores "laps" in list
(define (lap st)
(match-define (state start-time end-times) st)
(state start-time
(cons (current-seconds) end-times)))
;; stop : state -> list
;; stores final time, displays lap-times in h, m, s
(define (stop st)
(match-define (state start-time end-times*) st)
(define end-times (cons (current-seconds) end-times*))
(reverse
(map (lambda (an-end)
(let ((the-date (seconds->date(- an-end start-time))))
(list
(sub1(date-hour the-date))
;; sub1 is needed because (date-hour(seconds->date 0) = 1
(date-minute the-date)
(date-second the-date)))) end-times)))
在@Metaxal的回答中,您的“主循环”需要处理状态,并通过适当的函数“线程化”它:
用法示例:
(define (run)
(displayln "Enter 'lap' or 'quit':")
(let loop ([st (start)])
(match (read-line)
["quit" (stop st)]
["lap" (loop (lap st))]
[_ (loop st)])))
而@Óscar López的回答显示了SICP中解释的OOP风格
Racket(和Scheme)的一个好处是,您可以选择您认为最适合当前问题和您的口味的任何方法——简单命令式、面向对象命令式、纯功能性。严格来说,这是对我问题的回答。(请参阅我的编辑,了解我如何将其与@Greg Hendershot的用法示例结合使用)我将回到SICP!事实证明,(阅读行)的使用是我没有想到的一件简单的事情,这使我无法把它做好。
(define (run)
(displayln "Enter 'lap' or 'quit':")
(let loop ([st (start)])
(match (read-line)
["quit" (stop st)]
["lap" (loop (lap st))]
[_ (loop st)])))