123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687 |
- (post
- :title "Problem solving with Guile's prompts (call/cc)"
- :date (make-date* 2016 9 1)
- :tags '("guile" "prompts" "call/cc" "continuations")
- (h3 [(Also, first blog post!)])
- (p [Hello internets, thought I'd post some code I
- find interesting! Today, I want to introduce
- a really cool use case for Scheme's continuations.
- While continuations are a neat scheme feature, I struggled to find examples
- showing their usefulness in everyday programming. While
- solving a project euler problem, though, I happened on a
- solution I think is elegant and not easily implemented
- without them (Please let me know if y'all have a solution).
- Please note that I am using Guile's prompts as a substitute
- for call/cc, which I will explain next time I update the page])
- (h3 [The problem:])
- (p [It is possible to show that the square root of two can be
- expressed as an infinite continued fraction.])
- (p [√ 2 = 1 + 1/(2 + 1/(2 + 1/(2 + ... ))) = 1.414213...])
- (p [By expanding this for the first two iterations, we get:])
- (p [1 + 1/2 = 3/2 = 1.5])
- (p [1 + 1/(2 + 1/2) = 7/5 = 1.4])
- (p [The eighth expansion, 1393/985, is the first example where the number
- of digits in the numerator exceeds the number of digits in the
- denominator.])
- (p [In the first one-thousand expansions, how many fractions contain
- a numerator with more digits than denominator?])
- (h3 [The solution:])
- (p [Okay, pretty easy answer structure: Build a list of the first
- 1000 expansions, and filter those that pass the predicate.])
- (p [Very well, but what if, rather than find the first 1000
- expansions, the problem was stated this way:])
- (h3 [The problem, revised!])
- (p [Which expansion is the 500th example where the number of digits in
- the numerator exceed the number of digits in the denominator?])
- (h3 [Solution: revised!])
- (p [Since we don't know how many expansions we might enumerate before
- satisfying the predicate, we can't loop through a list of expansions like before.
- Instead, we need to continually test
- values until the predicate is satisfied.])
- (p [Generators can be useful here. Have a loop with three
- variables: an expansion generator, an index variable,
- and the current count of passed examples. We
- return the index when count >= 500.])
- (p [The difficulty here is implementing a generator for continued
- fractions. Unfortunately, it's not as easy to build as, say a
- fibonnaci generator, where the generator only needs to keep track
- of the two previous values. For a continued fraction generator,
- we need to remember the entire continued expasion, ie
- the FUNCTION STACK or CONTINUATION that produced the previous value!])
- (p [What we need is something that can capture the current
- function stack, add to it, save it, and then apply it to produce the next
- continued fraction. Guile prompts to the rescue! Using Guile's prompt
- primitives `call-with-prompt` and `abort-to-prompt` we write the following:])
- (code-block-scheme
- (car
- [(define (make-sqrt-2-generator)
- (let ((*k* #f) ; current expansion
- (tag (default-prompt-tag)))
- (λ ()
- (call-with-prompt
- tag
- (λ ()
- (if *k*
- (*k* (+ 2 (/ 1 (abort-to-prompt tag))))
- (+ 1 (/ 1 (abort-to-prompt tag)))))
- (λ (k)
- (set! *k* k)
- (k 2))))))]))
- (p [This generator constructor does what we want: It defines the variable
- *k* whose value is the current expansion, and returns a generator
- procedure. Each time the generator is applied, our prompt builds
- the next expansion using the previous one, and then passes
- it as a continuation to our default prompt handler, which sets it to *k*, and applies it to return the value of the continued fraction.])
- (p [Testing our new generator we see it produces the correct values:])
- (code-block-scheme
- (car
- [(let ((generate-expansion (make-sqrt-2-generator)))
- (do ((i 0 (1+ i)))
- ((> i 5))
- (format #t "~a, " (generate-expansion))))
-
- => 3/2, 7/5, 17/12, 41/29, 99/70, 239/169]))
- (p [Now that we have a generator for square root convergent fractions,
- solving the problem straightforward!]))
|