Renamed L.append to L.appendTo. This means that L.append is deprecated.
Code using it should be switched to use L.appendTo. This change was made due
to introducing new L.prependTo and L.assignTo lenses for similar partial
updates.
-L.append
+L.appendToDeprecated L.propsOf. L.propsOf was introduced to implement L.assign. Now
L.assignTo allows for a simpler implementation of L.assign and more.
Fixed L.subset not to call the predicate in case the focus is already
undefined.
Fixed L.query, L.findWith, and L.orElse (and other optics using L.orElse
underneath including e.g. L.choice and L.choices) to pass the outer index to
the optics passed as parameters.
Previously L.uriComponent only allowed strings to be encoded through it. Now
it also allows booleans and numbers similarly to e.g. Node's
Query String module. Because this
behavior was not previously documented or tested earlier, the change is
considered a bug fix.
Previously L.uri, L.uriComponent, and L.json threw an exception on invalid
inputs. Now they instead produce the error object as their result. This
behaviour was neither documented nor tested earlier, so the change is considered
a bug fix.
The current plan is to change Partial Lenses to support so called naked or
prototypeless objects with null prototype (i.e. Object.create(null)) in a
following major version. This is a breaking change although it is likely that it
will not affect most users. Usefully warning for this change of behaviour by
adding diagnostics to optics seems somewhat difficult.
Previously obsoleted L.iftes was removed.
The L.Constant functor was removed. It can be replaced as follows:
-L.constant
+{map: (_, x) => x}L.get has been changed to use the now exported L.Select applicative instead
of the removed L.Constant functor. The reason for this change is that it both
generalizes L.get and simplifies things overall.
L.get now works exactly like L.select. L.select and L.selectAs have been
consequently obsoleted. Change usages as follows:
-L.select(...)
+L.get(...)-L.selectAs(...)
+L.getAs(...)In the cases where L.get previously returned a valid result, the only
differences are in the cases where L.zero is involved. L.zero is used by
several other combinators that could be used as lenses including the
conditionals,
L.cond, andL.condOf,
and the querying combinators,
L.chain,L.choice,L.optional,L.unless, andL.when,
and the transform ops,
L.assignOp,L.modifyOp,L.removeOp, andL.setOp.
Previously L.zero was implemented so that it did something reasonable even
with a plain functor. Since no operation in this library now uses a plain
functor, the special case behaviour of L.zero has been removed. When
previously used with L.get, L.zero passed undefined to the inner optics
and ignored what the inner optics returned. For example, previously:
L.get(['x', L.when(x => x > 0), L.valueOr(0)], {x: -1})
// 0but now L.zero exits early:
L.get(['x', L.when(x => x > 0), L.valueOr(0)], {x: -1})
// undefinedThis is clearly a breaking change. However, this is unlikely to affect a large
number of use cases. To get the old behavior, use of L.zero needs to be
avoided. In the example case, one could write:
L.get(['x', L.ifElse(x => x > 0, [], R.always(0))], {x: -1})
// 0There is no longer guarantee that optic operations return newly allocated data
structures. In case all the elements of the result are the same, as determined
by
Object.is,
as in the input, optic operations may return the input as is. OTOH, there is
also currently no guarantee that input is returned as is.
Worked around an issue with React Native, see #161.
Tightened the specification of L.flatten and L.leafs to skip undefined
focuses. This is considered a bug fix as the behaviour wasn't previously
strictly specified.
Fixed a bug in L.filter, which didn't correctly handle the case of writing an
empty array in case the focus wasn't an array-like object.
Fixed a bug in L.condOf, which didn't handle the case of zero cases correctly.
Obsoleted L.iftes and added L.ifElse and L.cond as the replacements for
it. The motivation for the change is that formatting tools such as
Prettier cannot produce readable layouts for combinators
that use such non-trivial argument patterns. In cases where there is just a
single predicate, use L.ifElse:
-L.iftes(predicate, consequent, alternative)
+L.ifElse(predicate, consequent, alternative)In cases where there are multiple predicates, use L.cond:
-L.iftes(predicate1, consequent1,
- predicate2, consequent2,
- alternative)
+L.cond([predicate1, consequent1],
+ [predicate2, consequent2],
+ [alternative])As discussed in issue
#131, optics working on
arrays, objects, and strings, no longer remove empty values by default. It
appears that by default removal, on average, makes optics compositions more
complex. In most cases this change means that uses of L.define or L.required
with an empty value {}, [], or "", can simply be removed. L.define and
L.required now give a warning in case they are used with an empty value and a
matching empty value passes through them redundantly. In cases where removal of
empty values is desired, one can e.g. compose with L.defaults. In cases where
some default value is needed, one can e.g. compose with L.valueOr.
Removed previously obsoleted L.findHint.
As documented in 11.21.0:
- Support for lazy algebras with the
delayfunction was removed. L.cachewas removed.L.augmentwas removed.L.findandL.findWithwere changed to support a hint.L.findHintwas marked for removal.
Tightened the specification of a number of isomorphisms, including L.uri,
L.uriComponent, L.indexed, L.keyed, and L.reverse, so that their
inverses treat unexpected inputs as undefined. This is considered a bug fix as
the behaviour wasn't previously strictly specified.
L.cache was marked for removal. The main problem with L.cache is much like
with naïve memoize implementations: the cache is stored in the wrong place,
which is the point of definition of a cached (memoized) optic (function).
Instead, the cache storage should be at the point of use so that when the data
at the point of use is discarded so can the cache and that different points of
use can each have their own cache. Otherwise it is easy to have inefficient
caching and space leaks (keeping cache data around for too long).
L.augment was marked for removal. The reason for removing L.augment is that
the library nowadays allows most of L.augments functionality to be implemented
using simpler combinators such as L.pick with ordinary functions.
L.findHint was marked for merging into L.find. In the next major version
L.find will take an optional hint parameter like current L.findHint and
L.findHint will be marked for removal. Also, L.find will pass three
arguments to the predicate. The third parameter is the hint object.
L.findWith was marked to be changed to support a hint parameter. This means
that instead of taking multiple lenses as arguments to compose, L.findWith
will, in the next major version, take a single lens and an optional hint
parameter. To prepare use of L.findWith to be more compatible with the next
major version, simply pass an array of the lenses:
-L.findWith(...ls)
+L.findWith([...ls])Support for lazy algebras in the form of the delay operation was marked for
removal. The reason for removing support for lazy algebras is that the next
major version implements operations currently using lazy algebras, like
L.select, using a different technique that is significantly faster on current
JavaScript engines. That is because allocation of closures is very expensive on
current JavaScript engines and lazy algebras tend to result in allocating lots
of closures. Aside from performance issues, lazy algebras do, however, seem
solid, but having code supporting them without actually using them internally
for anything seems wasteful.
Fixed a bug in L.countIf. Previously it didn't pass the index to the predicate
as specified in the documentation.
It is now guaranteed that when traversals in this library build intermediate
lists of results and use of to create the initial empty list, the value used
for the empty list is a unique value not otherwise exported outside of this
library. In particular, the values 0, null, undefined, false, NaN, and
"" are not used as an empty list. This has the benefit that it is then
possible for client code to give such values special meaning in algebras.
Switched the order of arguments to optics so that the first two arguments are now the same as for an ordinary "read-only" function:
- (C, xi2yC, x, i) => ...
+ (x, i, C, xi2yC) => ...This way it is not necessary to distinguish between optics and read-only
functions in the get operation. On V8 based JavaScript engines this gives a
significant performance improvement in some operations as taking the length of
a function is very expensive in V8. This also means that the behavior of
composing optics and ordinary functions is different in the sense that more
arguments may be passed to an ordinary function. This change should only affect
a very small number of users who have written new optics directly against the
internal encoding. In such a case, you will need to switch the order of
arguments as shown in the above diff. Also, if you compose optics with ordinary
functions that may use more than two arguments, then you will need to limit
those functions two arguments.
Redesigned the experimental L.findHint. The main lesson in the redesign is
that the internally allocated local state for the hint was changed to be
explicitly allocated by the caller. This allows the caller to update the hint
and allows the search to be eliminated in more cases.
Previously L.append didn't provide its own index. Now it produces an index
that is the length of the focused array-like object or 0. This is considered a
bug fix as the behaviour wasn't previously strictly specified.
As discussed in issue
#50, to strongly guide
away from mutating data structures, optics now
Object.freeze
any new objects they create when NODE_ENV is not production. Note that
optics do not implicitly "deep freeze" data structures given to them or freeze
data returned by user defined functions. Only objects newly created by optic
functions themselves are frozen.
Removed previously obsoleted exports:
L.firstAs,L.first,L.just,L.mergeAs,L.merge, andL.to.
See previous changelog entries on how you should deal with those.
Lazy folds are no longer considered experimental. Note, however, that the
technique, an optional delay function, upon which they are based is currently
not included in the Static Land
specification. See issue 40
for discussion.
Renamed experimental L.first and L.firstAs as follows:
-L.first
+L.select
-L.firstAs
+L.selectAsThis was done to avoid confusing the operations on traversals with the newly
added L.last lens on array-like objects.
Obsoleted L.to and L.just. You can now directly compose optics with ordinary
functions (whose arity is not 4) and the result is a read-only optic. This makes
L.to the same as R.identity and L.just is the same as R.always.
Obsoleted L.mergeAs and L.merge. L.concatAs and L.concat are now just as
fast, so there is no need to have both.
L.augment, L.pick and L.props can now be written with an
instanceof Object or undefined. Other values are considered errors.
Previously they could be written with anything, but only a plain Object was
considered different from undefined.
L.slice and L.filter can now be written with an array-like object or
undefined. Other values are considered errors. This was the case earlier
already, but now it is asserted in non-production builds.
L.index no longer produces null for previously undefined elements. L.index
was changed in 4.0.0 to produce null elements. In 8.0.0 treatment of
array-like objects was relaxed, but array producing optics did not consistently
produce null elements. With the relaxed semantics it seems that producing
null values would complicate treatment of arrays, so it seems best to just
consistently produce arrays with undefined for previously undefined elements.
Relaxed treatment of objects and array like objects. Previously various optics
required objects to have either Object or Array as the constructor. Now any
instanceof Object is allowed where previously Object constructor was
required and a String or an Object with non-negative integer length is
allowed where previously Array constructor was required. This addresses issue
40. See the
documentation of L.prop and L.index for more details. The L.branch,
L.elems and L.values traversals have similarly relaxed treatment.
The previously deprecated L.sequence traversal was removed. You need to
explicitly choose either L.elems or L.values.
Previously undocumented, but accidentally tested for behavior of index lenses to
allow negative indices was removed. The old behavior was to ignore negative
indices. The new behavior is to throw an Error in non-production builds.
Behaviour in production builds is undefined.
Removed deprecated foldMapOf and collectMap. Use concatAs and collectAs
instead.
Index lenses previously supported using negative indices so that writing through a negative index was effectively a no-op. This behavior will not be supported in the next major version.
Deprecated L.sequence and introduced L.elems, which operates on arrays, and,
L.values, which operates on objects, to be used instead. L.sequence
originally only operated on arrays, but it was generalized to operate on objects
in 6.0.0. Unfortunately that turned out to be a mistake, because in the next
major version, 8.0.0, the plan is to relax the treatment of objects and array
like objects. The problem is that, with the generalized semantics, the type of
the result, object or array, when writing through L.sequence would depend on
the input in an uncontrollable manner. Apologies for the inconvenience!
Added minimal support for indexing. Various operations and combinators now provide an index value, either a number for an array index, or a string for an object property, or undefined in case there is no meaningful index, for the immediate index being addressed to the user-defined function taken by the operation or combinator.
Removed L.fromArrayBy. It was introduced as an experiment, but the use cases I
had in mind didn't seem to benefit from it. If you need it, you can use this:
const fromArrayBy = id =>
iso(
xs => {
if (R.is(Array, xs)) {
const o = {},
n = xs.length
for (let i = 0; i < n; ++i) {
const x = xs[i]
o[x[id]] = x
}
return o
}
},
o => (R.is(Object, o) ? R.values(o) : undefined)
)The lens L.nothing and the traversal L.skip were merged into a single
L.zero optic that works like L.nothing when being viewed, using L.get, and
otherwise like L.skip. The main benefit of this is that it allows "querying"
combinators L.chain, L.choice, and L.when use the one and same L.zero
and work without additional glue as traversals.
Generalized the L.sequence traversal to also operate on the values of objects.
Removed the defaul import. The array notation for composition is recommended as the shorthand of choice.
Marked the default import for removal. With the array shorthand for composition the default import is no longer worth keeping.
Reimplemented library internals using
Static Land style dictionaries,
switched to using infernals and dropped Ramda dependency and interop. These
changes were made for the following reasons:
-
infernalsis, and is supposed to remain, a tiny library. This is an advantage if one wishes to use lenses, but does not wish to use Ramda. -
Performance of traversals, and folds over traversals in particular, is and can now be significantly improved, because Static Land does not require wrapping or boxing primitive values.
To interop with Ramda, you can write:
import * as L from 'partial.lenses'
import * as R from 'ramda'
const fromRamda = ramdaLens => L.lens(R.view(ramdaLens), R.set(ramdaLens))
const toRamda = partialLens => R.lens(L.get(partialLens), L.set(partialLens))- Removed previously deprecated functionality:
removeAll. - Sparse arrays are no longer supported.
Although never explicitly specified in documentation, many of the operations and
combinators were curried using Ramda's curry. Unfortunately Ramda's curry is
very slow. From this version forward partial lenses no longer supports the
special features of Ramda's curry like placeholders.
Fixed bugs when removing a non-existing property from an object or a
non-existent index from an array. Previously L.remove("x", {}) returned {}.
Now it returns undefined as it was previously documented. Similarly
L.remove(index, []) now returns undefined as was documented.
Tightened the semantics of combinators, including L.index, L.filter,
L.prop and L.augment (and other combinators whose semantics are defined in
terms of those), that specifically work on objects or arrays. Previously such
combinators worked asymmetrically when operating on values not in their domain.
Now they consistently treat values that are not in their domain as undefined.
For example, L.get("x", null) now returns undefined (previously null) and,
consistently, L.set("x", 1, null) now returns {x: 1} (previously error).
Added minimalistic experimental traversal support in the form of the
sequence traversal.
Dropped implicit Ramda compatibility. To interop with Ramda, one must now
explicitly convert lenses using L.toRamda and L.fromRamda. In particular,
L.compose no longer necessarily returns a Ramda compatible lens and, in the
future, the implementation may be changed more drastically. This change was
made, because now a lens returned by L.compose can take less memory and it
will also be possible to further optimize the implementation in the future.
Removed deprecated functions L.view, L.over and L.firstOf.
Renamed L.view and L.over:
-L.view
+L.get-L.over
+L.modifyCalling deprecated functions now results in console.warn messages.
Deprecated L.firstOf and added L.choice, L.nothing and L.orElse that
allows the same (and more) functionality to be expressed more compositionally.
Changed from using a single default export to named exports to support dead-code
elimination, aka tree shaking. A number of combinators were renamed in the
process and the default import is now an alias for compose that may help to
keep notation concise.
Now using named exports and default that aliases compose:
-import L from "partial.lenses"
+import P, * as L from "partial.lenses"Module prefix no longer works as compose:
-L(...)
+P(...) or L.compose(...)default is a keyword and had to be renamed:
-L.default
+L.defaultsdelete is a keyword and had to be renamed:
-L.delete
+L.remove-L.deleteAll
+L.removeAll