post01_continuations.skr 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. (post
  2. :title "Problem solving with Guile's prompts (call/cc)"
  3. :date (make-date* 2016 9 1)
  4. :tags '("guile" "prompts" "call/cc" "continuations")
  5. (h3 [(Also, first blog post!)])
  6. (p [Hello internets, thought I'd post some code I
  7. find interesting! Today, I want to introduce
  8. a really cool use case for Scheme's continuations.
  9. While continuations are a neat scheme feature, I struggled to find examples
  10. showing their usefulness in everyday programming. While
  11. solving a project euler problem, though, I happened on a
  12. solution I think is elegant and not easily implemented
  13. without them (Please let me know if y'all have a solution).
  14. Please note that I am using Guile's prompts as a substitute
  15. for call/cc, which I will explain next time I update the page])
  16. (h3 [The problem:])
  17. (p [It is possible to show that the square root of two can be
  18. expressed as an infinite continued fraction.])
  19. (p [√ 2 = 1 + 1/(2 + 1/(2 + 1/(2 + ... ))) = 1.414213...])
  20. (p [By expanding this for the first two iterations, we get:])
  21. (p [1 + 1/2 = 3/2 = 1.5])
  22. (p [1 + 1/(2 + 1/2) = 7/5 = 1.4])
  23. (p [The eighth expansion, 1393/985, is the first example where the number
  24. of digits in the numerator exceeds the number of digits in the
  25. denominator.])
  26. (p [In the first one-thousand expansions, how many fractions contain
  27. a numerator with more digits than denominator?])
  28. (h3 [The solution:])
  29. (p [Okay, pretty easy answer structure: Build a list of the first
  30. 1000 expansions, and filter those that pass the predicate.])
  31. (p [Very well, but what if, rather than find the first 1000
  32. expansions, the problem was stated this way:])
  33. (h3 [The problem, revised!])
  34. (p [Which expansion is the 500th example where the number of digits in
  35. the numerator exceed the number of digits in the denominator?])
  36. (h3 [Solution: revised!])
  37. (p [Since we don't know how many expansions we might enumerate before
  38. satisfying the predicate, we can't loop through a list of expansions like before.
  39. Instead, we need to continually test
  40. values until the predicate is satisfied.])
  41. (p [Generators can be useful here. Have a loop with three
  42. variables: an expansion generator, an index variable,
  43. and the current count of passed examples. We
  44. return the index when count >= 500.])
  45. (p [The difficulty here is implementing a generator for continued
  46. fractions. Unfortunately, it's not as easy to build as, say a
  47. fibonnaci generator, where the generator only needs to keep track
  48. of the two previous values. For a continued fraction generator,
  49. we need to remember the entire continued expasion, ie
  50. the FUNCTION STACK or CONTINUATION that produced the previous value!])
  51. (p [What we need is something that can capture the current
  52. function stack, add to it, save it, and then apply it to produce the next
  53. continued fraction. Guile prompts to the rescue! Using Guile's prompt
  54. primitives `call-with-prompt` and `abort-to-prompt` we write the following:])
  55. (code-block-scheme
  56. (car
  57. [(define (make-sqrt-2-generator)
  58. (let ((*k* #f) ; current expansion
  59. (tag (default-prompt-tag)))
  60. (λ ()
  61. (call-with-prompt
  62. tag
  63. (λ ()
  64. (if *k*
  65. (*k* (+ 2 (/ 1 (abort-to-prompt tag))))
  66. (+ 1 (/ 1 (abort-to-prompt tag)))))
  67. (λ (k)
  68. (set! *k* k)
  69. (k 2))))))]))
  70. (p [This generator constructor does what we want: It defines the variable
  71. *k* whose value is the current expansion, and returns a generator
  72. procedure. Each time the generator is applied, our prompt builds
  73. the next expansion using the previous one, and then passes
  74. 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.])
  75. (p [Testing our new generator we see it produces the correct values:])
  76. (code-block-scheme
  77. (car
  78. [(let ((generate-expansion (make-sqrt-2-generator)))
  79. (do ((i 0 (1+ i)))
  80. ((> i 5))
  81. (format #t "~a, " (generate-expansion))))
  82. => 3/2, 7/5, 17/12, 41/29, 99/70, 239/169]))
  83. (p [Now that we have a generator for square root convergent fractions,
  84. solving the problem straightforward!]))