Giter VIP home page Giter VIP logo

lens's Introduction

lens Build Status codecov Scribble Docs

A Racket package for creating and composing pure functional lenses.

raco pkg install lens (require lens)

What on earth are lenses?

A lens is a value that can be used to focus on a small subpiece of some larger structure. A lens splits some data structure into two pieces - a view, which is some small isolated component of the data structure, and a context, which is everything else. The context can have a new view placed into it. This makes a lens act like a pure functional getter and setter:

> (lens-view first-lens '(1 2 3))
1
> (lens-set first-lens '(1 2 3) 'a)
'(a 2 3)

Lenses are first class values and pure functional, so they can be abstracted over and functions that operate on lenses can be created. For instance, given a lens its view can be "updated":

> (lens-transform first-lens '(1 2 3) number->string)
'("1" 2 3)

Additionaly, lenses are separate values from the objects they operate on, so they can be manipulated independently of any specific data. Functions can construct lenses, and operations can combine lenses. This allows for lens composition:

> (define first-of-b-key-lens (lens-compose first-lens (hash-ref-lens 'b)))
> (define a-hash (hash 'a '(1 2 3) 'b '(10 20 30) 'c '(100 200 300)))
> (lens-view first-of-b-key-lens a-hash)
10
> (lens-set first-of-b-key-lens a-hash 'foo)
#hash((a . (1 2 3)) (b . (foo 20 30)) (c . (100 200 300)))

Lenses can also be joined together to form compound lenses that view many things:

> (define first-third-fifth-lens (lens-join/list first-lens third-lens fifth-lens))
> (lens-view first-third-fifth-lens '(1 2 3 4 5 6))
'(1 3 5)
> (lens-set first-third-fifth-lens '(1 2 3 4 5 6) '(a b c))
'(a 2 b 4 c 6)

Lenses can also be extended to operate on some new data structure:

> (define first-of-each-lens (map-lens first-lens))
> (lens-view first-of-each-lens '((1 2) (3 4) (5 6)))
'(1 3 5)
> (lens-set first-of-each-lens '((1 2) (3 4) (5 6)) '(a b c))
'((a 2) (b 4) (c 6))

See the documentation for a full API reference

So when would I want to use lenses?

Lenses are most effective when you're dealing with the "giant ball of state" problem. When you have a large amount of state you need to pass around between code written in a functional style, it's difficult to update and manage it due to the lack of mutation "magically" updating your entire object graph when a function changes a small part of it. Lenses allow code to break down and manipulate portions of this state, simplifying interactions and updates.

In particular, consider using lenses if you find yourself doing any of the following:

  • Using a giant complex piece of state that most pieces of code only care about a small part of
  • Writing struct-copy a lot
  • Converting some hairy data structure into another one, manipulating it, then turning it back
  • Wishing you could treat data X as if it were a Y, i.e. "I wish this struct was a list so I could map over it easily"
  • Creating structs that have nested struct instances inside them.

For a more in depth introduction, see The Lens Guide. For detailed API documentation, see The Lens Reference.

lens's People

Contributors

alexknauth avatar dbarella avatar ecraven avatar gitter-badger avatar jackfirth avatar jackfirth-mj avatar lexi-lambda avatar racket-dep-fixer avatar stchang avatar suzannesoy avatar waffle-iron avatar wilbowma avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

lens's Issues

Subcollections should be documented

Currently the relevant subcollections are:

  • lens/base
  • lens/list
  • lens/hash
  • lens/syntax
  • lens/struct
  • lens/applicable (re-exports lens)

Note that once documented, changing the names or paths of subcollections is a breaking change. Therefore, prefer not to expose subcollections unless they are of significant size.

Lenses shouldn't be applicable by default, at all

Using lenses as raw functions is most of the time an error - it's far safer and more extensible (from the perspective of the lens package) to require all lens operations happen with lens-specific functions. Therefore, lenses should not be arbitrary two-value returning functions.

Argument orders of lens functions should follow some formal convention

See #72 for the discussion that prompted this issue

UPDATE

The argument order conventions are, in order of decreasing priority, as follows:

  1. Singular arguments before variadic arguments.
  2. Plural arguments should be expressed as variadic arguments.
  3. Twice-plural functions must take their plural arguments in one-to-one correspondence and should take them as a series of pairs in variadic arguments
  4. Lens arguments before target arguments before view arguments.
  5. transform functions should follow the same conventions with their transformer arguments taking the place of view arguments.

apply-lens is a bad name

Lenses aren't really supposed to be interpreted as functions any more outside of applicable lenses. Non exhaustive list of possible better names:

  • lens-view/context
  • lens-view+context
  • focus-lens
  • lens-values

Applicable lens parameter name is misleading

There's no longer a way to use applicable lenses in arbitrary expressions instead of on a module-by-module basis, and there's no longer any cases where there's ambiguity over whether lenses are currently applicable.

Syntax lenses are very single-purpose

I initially made these for use in very specific problems in other projects. They don't really belong in the general lens package, at least not with the same status as the other lenses. Possible fixes include:

  • Move to a lens/unstable collection or something that similarly signals they're not to be relied upon heavily. This would prevent them from getting locked into the API forever with 1.0
  • Move to an entirely separate package which provides a lens/syntax collection. There would probably have to be more syntax lenses to justify this, but it would give the most flexibility as that package could be versioned and managed separately.
  • Remove them from the package catalog entirely and just keep them as private to wherever I needed them originally.

They should probably be excluded from 1.0 and hidden until it's decided what to do with them.

README is out of date

The readme reflects the old function-based definition of lenses and needs to be updated.

Isomorphisms

An isomorphism is a pair of functions such that each inverts the other, and thus create a one-to-one mapping between two sets of values. An example is string->symbol and symbol->string. A lens can always be constructed from an isomorphism, and when the resulting lens is focused on a target the context is always "empty", that is, no two targets with the same view will have different contexts. Isomorphisms are provided by the Haskell lens package, and they're nice for extending a lens to work with a different data type. It may be worthwhile to provide them in this package.

The package should be versioned

The version format should be equivalent to the last two portions of semantic versioning - per Racket package guidelines, major versions (which are allowed to be backwards incompatible) should be new packages. Backwards incompatibility should be allowed before committing to a 1.0 version.

No exported identifiers should be undocumented

This can be achieved with a doc-coverage test. To temporarily solve the issue for failing exports when this test is added, exports should be excluded (such as with except-out) to get the test to pass. Then work can be done on documenting these exports.

The package should be on version 0.1

All changes until 1.0 should bump up the patch version (e.g. 0.2, 0.3, ... 0.42). Any change may break backwards compatibility with previous versions until 1.0.

Lens view/set/transform derivatives should be consistent

Vanilla view and set obey the lens laws, and transform is immediately derivable from them. For variations of these forms, e.g. view*, set*, transform*, view-all, set-all, and any others we come up with, the view and set forms should obey some reasonable variation of the lens laws and the transform form should be derivable from them.

Exports should be contracted

Lens contracts need not fully enforce their view and target contracts on first pass, but that should be possible to do in a later iteration

There should be a stable 1.0 release

This issue will be updated with a list of other issues that need to be resolved before 1.0 will be committed to.

Definite requirements:

  • Contracted exports (#22)
  • Versioning (#18 and #43)
  • Documentation coverage (#34)
  • Up-to-date readme (#42)
  • Subcollections should either not be exposed or be exposed with their final names (#44, #45, and #46) (While this isn't done, they're no longer exposed except for lens and lens/applicable so this no longer blocks 1.0)
  • apply-lens should be renamed (#40)
  • Lens laws documentation (#21)
  • Correct package name (#19)
  • Standardized API function argument orders (#73)
  • Consistency among view/set/transform variations (#80)
  • Something needs to be done about syntax lenses (#84) (Also not done per se, but it's been removed as a 1.0 blocker.)

This list is currently non-final, more additions may arise. Once this list is both final and completely resolved, the package will be marked version 1.0 and backwards compatibility will be required for future releases.

`lens-view*` and `lens-set*` don't match in behavior

lens-view* is yields one view from multiple lenses, whereas lens-set* is for manipulating multiple views with multiple lenses. They don't match in plurality.

Possible fix: make lens-view* return a list of views, one for each lens, and make two separate functions, lens-view/thrush and lens-set/thrush or something like that which are one-target, one-view, multiple-lens and perform the composition.

Falsy lenses

Sometimes it's handy to break the lens laws, just a little bit. Specifically, it might be useful to have a lens that is allowed to "miss" its target, for which viewing returns false and setting is a no-op. There could even be a general function that given a predicate to determine when a lens won't miss, constructs an improper "falsy lens" which obeys the lens laws for all values the predicate returns true for, but for other values always views false and setting does nothing and returns the value unchanged. Currently, the syntax keyword sequence lenses are falsy lenses (but this behavior is undocumented).

Keyword-based syntax lenses

There needs to be lenses for focusing on things such as "the first syntax object immediately after the first appearance of this keyword" or "all syntax objects after this keyword but before any other keyword". Required for jackfirth/command-line-ext#6

hash-ref-lens should be consistent with list-ref-lens

Possible fix: Make nested-ref combined lens form separate from the singular form, as is the case with list-ref-lens/list-ref-nested-lens. This isn't necessary for 1.0 as hash-ref-lens isn't exposed or documented.

Would it make sense to add a lens structure with a prop:procedure that allows it to be used as the getter?

So for example:

(define fst (make-lens/struct first set-first))
(fst '(1 2 3)) ; -> 1
(lens-veiw fst '(1 2 3)) ; -> 1 ; both work
(lens-set fst '(1 2 3) 5) ; -> '(5 2 3)

This would mean that fst could be a replacement for first, but with it also being a struct with lens info so that it can be composed with other lenses and used with lens functions.

Does this look like something that either you or I could add to this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.