diff --git a/lenses/lenses.scrbl b/lenses/lenses.scrbl index c959674..a99eb87 100644 --- a/lenses/lenses.scrbl +++ b/lenses/lenses.scrbl @@ -2,6 +2,7 @@ @(require scribble/eval (for-label lenses + racket/contract racket/base)) @(define lenses-eval (make-base-eval)) @@ -21,3 +22,82 @@ setters in object-oriented languages. @author[@author+email["Jack Firth" "jackhfirth@gmail.com"]] source code: @url["https://github.com/jackfirth/lenses"] + +@section{Core Lens Forms} + +@defproc[(lens/c [target/c contract?] [view/c contract?]) contract?]{ + Contract constructor for lenses. A lens is a function that takes one + value, its @italic{target}, and returns two values, a @italic{view} + and a @italic{context}. The context is a function that takes a new view + value and "replaces" the old view value with the new value, giving a + modified target. Less technically, a lens is a way to analyze some + specific piece of a @racket[target/c] that is a @racket[view/c], + along with a way to replace that piece with a new view value. Lenses + deconstruct and reconstruct data by examinimg small portions of their + structure. In terms of contracts, a @racket[(lens/c target/c view/c)] + is equivalent to the following function contract: + @racketblock[ + (-> target/c + (values view/c + (-> view/c target/c))) + ] + + An example is the @racket[first-lens], which is a lens for examiniming + specifically the first item in a list: + @lenses-examples[ + (first-lens '(1 2 3)) + (let-values ([(_ context) (first-lens '(1 2 3))]) + (context 'a)) +]} + +@defform[(let-lens (view-id context-id) lens-call-expr body ...)]{ + Restricted form of @racket[let-values] specifically for working with + the return values of a lens function. This is purely for semantic + clarity and to eliminate a few extra parens. + @lenses-examples[ + (let-lens (view context) (first-lens '(1 2 3)) + (printf "View is ~a\n" view) + (context 'a)) +]} + +@defproc[(lens-view [lens (lens/c target/c view/c)] [target target/c]) view/c]{ + Extracts only the view of @racket[target] with @racket[lens], disregarding + the context. Essentially a getter function. + @lenses-examples[ + (lens-view first-lens '(1 2 3)) +]} + +@defproc[(lens-set [lens (lens/c target/c view/c)] [target target/c] [new-view view/c]) target/c]{ + Sets the view of @racket[target] to @racket[new-view] using @racket[lens]. + Shorthand for getting the context of @racket[target] with @racket[lens], + then calling that context function with @racket[new-view]. Essentially + a setter function. + @lenses-examples[ + (lens-set first-lens '(1 2 3) 'a) +]} + +@defproc[(lens-transform [lens (lens/c target/c view/c)] + [target target/c] + [transformer (-> view/c view/c)]) + target/c]{ + Transforms the view of @racket[target] through the given @racket[lens] + with the @racket[transformer] function. Equivalent to getting the + view of @racket[target] through @racket[lens], passing that value + to @racket[transformer], then setting the view of @racket[target] + to the return value of calling @racket[transformer] with the old + view. + @lenses-examples[ + (lens-transform first-lens '(1 2 3) number->string) +]} + +@defproc[(lens-compose [lens proc] ...+) proc?]{ + Composes the given lenses together into one @italic{compound lens}. + The compound lens operates similarly to composed functions do in + that the last @racket[lens] is the first @racket[lens] the compound + lens's target is viewed through. Each successive lens "zooms in" + to a more detailed view. + @lenses-examples[ + (define first-of-second-lens (lens-compose first-lens second-lens)) + (lens-view first-of-second-lens '((1 a) (2 b) (3 c))) + (lens-set first-of-second-lens '((1 a) (2 b) (3 c)) 200) +]} \ No newline at end of file