/zipper/zipper.rkt

http://github.com/ntoronto/plt-stuff · Racket · 191 lines · 136 code · 30 blank · 25 comment · 14 complexity · 49b364861d3718c1923af33021c893e6 MD5 · raw file

  1. #lang racket/base
  2. ;; todo: unzip integers like vectors of bits?
  3. (require racket/match racket/list
  4. (rename-in racket/vector [vector-copy racket-vector-copy])
  5. (for-syntax racket/base unstable/syntax))
  6. (provide (except-out (all-defined-out) vector-copy))
  7. (define vector-copy
  8. (case-lambda
  9. [(v) (racket-vector-copy v)]
  10. [(v s) (if (= s (vector-length v))
  11. (make-vector 0)
  12. (racket-vector-copy v s))]
  13. [(v s e) (if (= s e (vector-length v))
  14. (make-vector 0)
  15. (racket-vector-copy v s e))]))
  16. ;; zipper
  17. ;; Has 1. path : (listof (B -> A)), a path from the zipper back to the root,
  18. ;; which is of type A
  19. ;; 2. value : B, the value currently in focus
  20. (struct zipper (path value) #:transparent)
  21. (define (new-zipper value) (zipper null value))
  22. ;; define-unzip
  23. ;; Given a getter and a functional setter, defines an unzip function
  24. (define-syntax (define-unzip stx)
  25. (syntax-case stx ()
  26. [(_ unzip field-ref field-set)
  27. (identifier? #'unzip)
  28. (syntax/loc stx
  29. (define (unzip z)
  30. (match-let ([(zipper path v) z])
  31. (let ([field-set (λ (e) (field-set v e))])
  32. (zipper (cons field-set path) (field-ref v))))))]))
  33. ;; zip : zipper [nonnegative-integer] -> zipper
  34. ;; Applies the functional updates in the path to zipper-value, and returns
  35. ;; a new zipper with a shorter path
  36. (define zip
  37. (case-lambda
  38. [(z)
  39. (match-let ([(zipper path v) z])
  40. (if (empty? path)
  41. (error 'zip "expected a zipper with a non-empty path, got ~e" z)
  42. (match-let ([(cons field-set path) path])
  43. (zipper path (field-set v)))))]
  44. [(z n)
  45. (match-let ([(zipper path v) z])
  46. (when ((length path) . < . n)
  47. (error 'zip "expected a zipper with path length >= ~a, got one with path length ~a: ~e"
  48. n (length path) z))
  49. (let loop ([path path] [v v] [n n])
  50. (if (zero? n)
  51. (zipper path v)
  52. (match-let ([(cons field-set path) path])
  53. (loop path (field-set v) (sub1 n))))))]))
  54. ;; zip* : zipper -> zipper
  55. ;; Zips a zipper all the way
  56. (define (zip* z)
  57. (match-let ([(zipper path v) z])
  58. (let loop ([path path] [v v])
  59. (if (null? path)
  60. (zipper path v)
  61. (match-let ([(cons field-set path) path])
  62. (loop path (field-set v)))))))
  63. (define (zipper->value z)
  64. (zipper-value (zip* z)))
  65. ;; zipper-set : zipper any -> zipper
  66. ;; Functionally updates a zipper's current value
  67. (define (zipper-set z value)
  68. (zipper (zipper-path z) value))
  69. ;; =============================================================================
  70. ;; unzippers for built-in data types
  71. ;; conses
  72. (define (unzip-car z)
  73. (match-let ([(zipper path (cons a b)) z])
  74. (let ([cons-car (λ (a) (cons a b))])
  75. (zipper (cons cons-car path) a))))
  76. (define (unzip-cdr z)
  77. (match-let ([(zipper path (cons a b)) z])
  78. (let ([cons-cdr (λ (b) (cons a b))])
  79. (zipper (cons cons-cdr path) b))))
  80. ;; proper lists
  81. (define (unzip-first z)
  82. (match-let ([(zipper path (list a b ...)) z])
  83. (let ([cons-first (λ (a) (cons a b))])
  84. (zipper (cons cons-first path) a))))
  85. (define (unzip-rest z)
  86. (match-let ([(zipper path (list a b ...)) z])
  87. (let ([cons-rest (λ (b) (cons a b))])
  88. (zipper (cons cons-rest path) b))))
  89. ;; vectors
  90. (define (unzip-vector z i)
  91. (match-let ([(zipper path v) z])
  92. (let* ([new-v (vector-copy v)]
  93. [vector-set (λ (e) (vector-set! new-v i e) new-v)])
  94. (zipper (cons vector-set path) (vector-ref v i)))))
  95. (define (unzip-vector-left z n)
  96. (match-let ([(zipper path v) z])
  97. (let* ([right (vector-copy v n)]
  98. [vector-prepend (λ (left) (vector-append left right))])
  99. (zipper (cons vector-prepend path) (vector-copy v 0 n)))))
  100. (define (unzip-vector-right z n)
  101. (match-let ([(zipper path v) z])
  102. (let* ([left (vector-copy v 0 n)]
  103. [vector-postpend (λ (right) (vector-append left right))])
  104. (zipper (cons vector-postpend path) (vector-copy v n)))))
  105. (define (unzip-subvector z start end)
  106. (match-let ([(zipper path v) z])
  107. (let* ([left (vector-copy v 0 start)]
  108. [right (vector-copy v end)]
  109. [vector-insert (λ (mid) (vector-append left mid right))])
  110. (zipper (cons vector-insert path) (vector-copy v start end)))))
  111. ;; strings
  112. (define (unzip-string z i)
  113. (match-let ([(zipper path s) z])
  114. (let* ([new-s (string-copy s)]
  115. [string-set (λ (c) (string-set! new-s i c) new-s)])
  116. (zipper (cons string-set path) (string-ref s i)))))
  117. (define (unzip-string-left z n)
  118. (match-let ([(zipper path str) z])
  119. (let* ([right (substring str n)]
  120. [string-prepend (λ (left) (string-append left right))])
  121. (zipper (cons string-prepend path) (substring str 0 n)))))
  122. (define (unzip-string-right z n)
  123. (match-let ([(zipper path str) z])
  124. (let* ([left (substring str 0 n)]
  125. [string-postpend (λ (right) (string-append left right))])
  126. (zipper (cons string-postpend path) (substring str n)))))
  127. (define (unzip-substring z start end)
  128. (match-let ([(zipper path s) z])
  129. (let* ([left (substring s 0 start)]
  130. [right (substring s end)]
  131. [string-insert (λ (mid) (string-append left mid right))])
  132. (zipper (cons string-insert path) (substring s start end)))))
  133. ;; =============================================================================
  134. ;; unzippers for structs
  135. ;; define-struct-unzips
  136. ;; Defines unzip functions for the given fields of the given struct
  137. ;; Should work everywhere struct-copy works
  138. (define-syntax (define-struct-unzips stx)
  139. (syntax-case stx ()
  140. [(_ struct-id (field ...))
  141. (and (identifier? #'struct-id)
  142. (andmap identifier? (syntax->list #'(field ...))))
  143. (with-syntax ([(unzip-field ...)
  144. (map (λ (field)
  145. (format-id field "unzip-~a-~a" #'struct-id field))
  146. (syntax->list #'(field ...)))]
  147. [(field-set ...)
  148. (map (λ (field)
  149. (format-id field "~a-~a-set" #'struct-id field))
  150. (syntax->list #'(field ...)))]
  151. [(field-ref ...)
  152. (map (λ (field)
  153. (format-id field "~a-~a" #'struct-id field))
  154. (syntax->list #'(field ...)))])
  155. (syntax/loc stx
  156. (begin (define (unzip-field z)
  157. (match-let ([(zipper path v) z])
  158. (let ([field-set (λ (e)
  159. (struct-copy struct-id v [field e]))])
  160. (zipper (cons field-set path) (field-ref v)))))
  161. ...)))]))