qim

Immutable/functional select/update queries for plain JS.

qim

Immutable/functional select/update queries for plain JS.

WARNING: Qim is already useful, but it's still considered experimental. It might have some rough edges, and the API might change!

Qim makes it simple to reach in and modify complex nested JS objects. This is possible with a query path that is just a simple JS array, much like you might use with set and update from Lodash, but with a more powerful concept of "navigators" (borrowed from Specter, a Clojure library). Instead of just string keys, Qim's navigators can act as predicates, wildcards, slices, and other tools. Those same navigators allow you to reach in and select parts of JS objects as well.

Qim's updates are immutable, returning new objects, but those objects share any unchanged parts with the original object.

Qim's API is curried and data last, so it should fit well with other functional libraries like Lodash/fp and ramda.

And Qim does its best to stay performant!

Contents

A simple (kind-of-contrived) example

Let's start with some data like this:

----- ----- - -
  ------ -
    ---- -
      ----- -
        ------ ------
        ----- -----
      --
      ------ -------
    --
    ----- -
      ----- -
        ------ -------
        ----- -----
      --
      ------ -------
    -
  --
  ------ -------
--

Let's import a couple things from Qim:

------ -------- ------ ---- ------

Now let's grab all the first names:

----- ---------- - ---------------- ------ ------- --------- -------

(We'll explain $each a little more later, but you can probably guess: it's like a wildcard.)

firstNames now looks like:

------- -------

Let's import a couple more things:

------ -------- ------- ---- ------

And now we can upper-case all our first names.

----- -------- - ---------------- ------ ------- --------
  ---------------- -- ------------------------
-- -------

Notice we used the same path from our select but added an $apply to do a transformation. (Again, we'll explain $apply better in the next section.)

After that, newState looks like:

----- ----- - -
  ------ -
    ---- -
      ----- -
        ------ ------
        ----- -----
      --
      ------ -------
    --
    ----- -
      ----- -
        ------ -------
        ----- -----
      --
      ------ -------
    -
  --
  ------ -------
--

Just for comparison, let's grab the first names with plain JS:

----- ---------- - ------------------------
  ------------- -- ----------------------------------

That's not too bad, but this is a very simple example. The $each from Qim makes things a lot more expressive. Let's look at Lodash/fp and Ramda too:

----- ----- ---- ---- ------------

----- --------- - -----
  -------------
  ---------------- ----------
---------
------ - ---- --------

----- --------- - -------
  ----------------
  ---------
  --------------------- ----------
---------

Lodash/fp is nice and expressive, but it costs a lot in terms of performance.

Test Ops/Sec
native 2,223,356
lodash/fp flow 17,110
Ramda pipe 278,705
qim select 953,949

Ramda performs a lot better, but it's a little less concise.

Qim is slower than native, but it's doing more than the native equivalent, because it's accounting for things like missing keys. And as you'll see soon, it has a lot more expressive power.

That update in plain JS is a lot more verbose, even for this really simple example:

----- -------- - -
  ---------
  ------ ------------------------
    --------------- --------- -- -
      ----- ---- - ----------------------
      --------------- - -
        --------
        ----- -
          -------------
          ------ -----------------------------
        -
      --
      ------ ------
    -- ---
--

So we go with something like Lodash/fp:

----- -------- - ------------------ -------------
  ------------------ --------- --------- -- ------------------------
-- ------

Or Ramda:

--------------------------- ------
  -------------------------- ---------- --------- -- ------------------------
-- ------

Again, performance is going to take a hit for Lodash/fp.

Test Ops/Sec
native 300,219
lodash/fp update 16,663
Ramda update 117,961
qim update 176,196

Ramda is much faster, but we've had to start using lenses. Again, native is the fastest, but at a cost of being awfully unreadable. Qim's main goal isn't to be performant but rather to be expressive. Lodash/fp looks pretty nice, but remember how closely the update resembled the select with Qim? With Lodash/fp, an update is a different animal. With Ramda, we've had to switch to completely different concepts. As we'll see with a more complex example, Qim will retain its simple, expressive query power for updates while Lodash/fp and Ramda are going to get more complicated.

A more complex (not-too-contrived) example

Let's start with some data like this:

----- ----- - -
  ------- -
    -------- -
      ---- -
        ------ ------
        ----- ----------
        -------- --
      --
      ---- -
        ------ -------
        ----- ----------
        -------- ----
      --
      ---- -
        ------ ------
        ----- -----------
        -------- --
      -
    -
  -
--

Let's say we want to change our state so that for every savings account, we:

  1. Add 5% interest to any balance > 1000.
  2. Subtract 10 from any balance < 100. Cause fees are how banks make money, right?

(And I know banks should have transactions, yada, yada.)

Okay, drum roll... with Qim, we can do that like this:

------ -------- ------ ------- ---- ------

----- -------- - ----------------- ---------- ------
  ------- -- ------------ --- ---------- ----------
  ---- -- --- -- ----- ---------- -- --- - -------
  ---- -- --- - ---- ---------- -- --- - ----
-- -------

Even without any explanation, hopefully you have a rough idea of what's going on. Like we saw in the simple example with $each and $apply, instead of only accepting an array of strings for a path, Qim's update function accepts an array of navigators. Using different types of navigators together creates a rich query path for updating a nested object. We'll look closer at this particular query in a bit, but first let's try the same thing with vanilla JS.

----- -------- - -
  ---------
  ------- -
    ----------------
    -------- ------------------------------------------------- --- -- -
      ----- ------- - -------------------------
      -- ------------- --- ---------- -
        -- ---------------- -- ----- -
          ---------- - -
            -----------
            -------- --------------- - ----
          --
          ------ -------
        -
        -- ---------------- - ---- -
          ---------- - -
            -----------
            -------- --------------- - --
          --
          ------ -------
        -
      -
      ---------- - --------
      ------ -------
    -- ---
  -
--

Yuck. That is ugly. Lots of references to things we don't really care about. Okay, hopefully nobody writes code like that. Let's use Lodash/fp to clean that up.

------ -- ---- ------------

----- -------- - -------------------- ----------- -------------------- --
  ------------ --- --------- - -
    -------------------- ---------
      ---- -- --- -- ----- --- -- --- - ------
      ---- -- --- - ---- --- -- --- - ----
      ------------- --- -- ----
    --- --------
  - - -------
-- ------

Okay, that's a lot more concise, but there are still some problems:

  1. We have to return account in the case where it's not a savings account. Our original requirement was really to filter out savings accounts and operate on those, but we can't really do that, because we want to modify the whole state. Using a filter would strip out the accounts we don't modify.
  2. Similarly, we have to return the balance even if it's not a low or high balance that we want to change.
  3. If we nest deeper and break out of point-free style, it gets pretty awkward to write or read the code. We could clean that up by splitting this into multiple functions, but remember how concise the requirement is vs the resulting code complexity.
  4. If none of our accounts actually match these criteria, we'll still end up with a new state object.

Qim boils this down to the essential declarative parts, using an expressive query path, and it avoids unnecessary mutations.

Let's stretch out the previous example to take a closer look at some of the navigators used.

----- -------- - --------
  -- - ------ --------- -- ---- --- -- --- -------
  --------- ----------
  -- ----- -- ---- - -------- ---- ------- ---- ----- -- -- ------ -- ------
  ------
  -- --------- --- ---- ---------- --- -------- ---- -- -- --------
  ------- -- ------------ --- ----------
  -- ------- --- ----------
  ----------
  -- ------ --- ---- ------ ------- --- ---- ----------
  -
    -- ------- --------- -- ---- --- - ---- --------
    --- -- --- -- -----
    -- ------ -- ---- -- --------- ---- ---- -- --- -------
    ---------- -- --- - -----
  --
  -- ------ ---- ------
  -
    -- ------- --------- -- ---- --- - --- --------
    --- -- --- - ----
    -- ------ --- --------- -------- ------ --- ----- ---- ------
    -- -- -- -- -------- --------------- -- --------- ------
    ---------- -- --- - ---
  -
-- -------

Because navigators are only ever working on the part of the object that you've navigated to, you don't ever have to worry about the parts of the object that you don't touch. Those parts remain intact.

These modifications are immutable, and they share unmodified branches:

-------------------- --- -------
-- ----
------------------------- --- ------------
-- ----

Changing something to its current value is a no-op:

----- -------- - ----------------- ---------- ---- ------- -- -- ------------ -------
-------------------- --- -------
-- ----

And of course these navigators are useful for selecting data too. Instead of modifying an object, the select method navigates to each matching part of the query and returns all the matching parts in an array.

------ -------- ---- ------

----- ----- - ----------------- ---------- ------ --------- -------
-- ------- ------- ------

Let's get a little more fancy. Let's grab all the usernames of people that have high balances.

------ ----- ---- ------

-- --- --------- --- -------- -- --- --- ----- --- --- ---- -- --- - ---------
----- -------------- - --------------- --- -- --- -- -------

----- --------- - ----------------- ---------- ------ --------------- --------- -------
-- --------

has checks if a selection returns anything. We use currying to create a function for checking if an account balance is high, and we use that as a predicate to select the owners with a high balance.

Cool, huh?

Installation

--- ------- --- ------

Usage

All functions and navigators are available as named exports:

------ -------- ------- ------ ------- ---- ------

Or of course you can just import everything:

------ - -- --- ---- ------

You can also import individual functions. If you import from qim, then don't do this! qim points to a bundle that includes all the individual modules, so importing individual modules will import them again.

------ ------ ---- -------------
------ ------ ---- -------------
------ ----- ---- ------------
------ ------ ---- -------------

If you npm install qim, you'll have UMD builds in node_modules/qim/build/umd/qim.js and node_modules/qim/build/umd.min.js.

API

All methods in this section are curried, meaning if you leave off any of the arguments, you'll get a function that takes the remaining arguments. Specifically, this is most useful for the last object parameter. For example:

----- ------------------ - ------------------- ------ ------------- -------------
----- -------------------- - --------------------------

apply(query, transform, object)

Just a convenience method for updating with a single transform ($apply) function.

------
  --------- ------ --------
  ---- -- -------------------
  ------- ----- ------ --------
-
-- ------- ----- ------ --------
------
  ----------- ------ ----- -- ----- - - --- ---
  --- -- --- - ----
  --------- --- -- -- -- -- ---
-
-- --------- --- -- -- --- -- ----

find(query, object)

Like select, but only returns a single result. If many results would be returned from a select, it will return the first result.

-----
  ------- ----- -- ----- - - --- ---
  --- -- -- -- -- --
-
-- -

Generally, this will perform much better than taking the first item of the array returned by a select.

has(query, object)

Returns true if an object has a matching result.

----
  ------- ----- -- ----- - - --- ---
  --- -- --
-
-- ----
----
  ------- ----- -- ----- - - --- ---
  --- -- --
-
-- -----

select(query, object)

Returns an array of selected results from an object.

-------
  ----------- ------ ----- -- ----- - - --- ---
  --------- --- -- -- -- -- ---
-
-- --- -- --

set(query, value, object)

Just a convenience method to set a query path to a constant value.

----
  --------- ------ --------
  ---------
  ------- ----- ------ --------
-
-- ------- ----- ------ -----------
----
  ----------- ------ ----- -- ----- - - --- ---
  --
  --------- --- -- -- -- -- ---
-
-- --------- --- -- -- -- -- ---

update(query, object)

Returns a mutation of an object without changing the original object.

-------
  --------- ------ ------- ----------- -- ---------------------
  ------- ----- ------ --------
-
-- ------- ----- ------ --------
-------
  ----------- ------ ----- -- ----- - - --- -- ------------ -- ----- - ----
  --------- --- -- -- -- -- ---
-
-- ----------- --- -- -- -- -- ----

Navigators

Built-in, type-based navigators

Key (string/integer)

Navigates to that key of an object/array.

-------
  --------- ------- -------
  ------- ------ ----- --------
-
-- -----
-------
  ----- -----
  ------ ----- ----- -----
-
-- -----
-------
  --------- ------- ------ ----------------
  ------- ------ ----- -----------
-
-- --------- -------- ------- -----------
-------
  ----- ---- ------------- -- -----------------------
  ------ ----- ----- -----
-
-- ------ ----- ----- -----

Predicate (function)

Passes the currently navigated value to the function and continues navigating if the function returns true.

-------
  ------- ----- -- ----- - ---
  ---- --- -- -- -- --
-
-- --- -- --
-------
  ------- ----- -- ----- - -- ---------
  ---- --- -- -- -- --
-
-- ---- --- -- -- -- --

Nested (array)

Branches and performs a sub-query. Mainly useful for update, since you may want to update different branches of an object in different ways. You can branch with select, but this is less useful since you typically want to select homogenous value types.

-------
  -------
    ----- -------- -- - - ----
    ----- -------- -- - - ----
  --
  ---- -- -- --- --- -- -- ---
-
-- ---- -- -- ---- --- -- -- ----

Stop (undefined/null)

Stops navigation. This is most useful inside $nav or custom path navigators to stop navigation for certain values.

------------ ---------- ----- --- --- ----
-- --
------------ ---------- ---- ------------ -- ----- - ---- --- --- ----
-- --- --- ---

For nested queries, undefined/null only stops the current nested query, not the whole query.

--------
  ----- ---------- -----
  ----- ----
-- --- --- ------ -- --- -------
-- ------
--------
  ----- ---------- ---- -------- -- ------------------
  ----- ---- -------- -- -----------------
-- --- --- ------ -- --- -------
-- --- --- ------ -- --- ------

Named navigators

By convention, all navigators are prefixed with a $. This is mainly intended to visually distinguish them in a query path. But it also is meant to distinguish them from normal functions. Navigators are declarative, meaning they represent a navigation to be performed, rather than actually doing an operation.

$apply(fn)

Transforms the currently navigated value using the provided function. Typically used for update, but can be used for select to transform the values selected.

-------
  ----------- ------ ------------ -- ----- - ----
  --------- --- -- -- ---
-
-- --- -- -- --
-------
  ------- ------------ -- ----- - ----
  --------- --- -- -- ---
-
-- --------- --- -- -- ---

$begin

Navigates to an empty list at the beginning of an array. Useful for adding things to the beginning of a list.

-------
  -------- --------- --- -----
  --- -- --
-
-- ---- --- -- -- -- --

$default(value)

By default, Qim will create missing objects when you try to update a path that doesn't exist. You can use $default to change this behavior and provide your own default value.

--------- ------------- --- ---- ---
-- --- ------
--------- ------------ ---- ----- -- ---
-- --- --- -- -- ---
------------- -------------- ------ --- ------ ---
-- ------- ------- -----

$each

Navigates to each value of an array or object.

-------
  ------- ---------- -- --- - ----
  --- -- --
-
-- --- -- --
-------
  ------- ---------- -- --- - ----
  --- -- -- -- -- --
-
-- --- -- -- -- -- --
-------
  --------
  --- -- --
-
-- --- -- --
-------
  --------
  --- -- -- -- -- --
-
-- --- -- --

$eachKey

Navigates to each key of an array or object.

-------
  ---------- ---------- -- --------------------
  --- -- -- -- -- --
-
-- --- -- -- -- -- --
-------
  -----------
  --- -- -- -- -- --
-
-- ----- ---- ----

$eachPair

Navigates to each key/value pair of an array or object. A key/value pair is just an array of [key, value].

-------
  ----------- ------------- ------- -- ------------------- ----- - -----
  --- -- -- -- -- --
-
-- --- -- -- -- -- --
-------
  -----------
    --- ---------- -- --------------------
    --- ------------ -- ----- - ---
  --
  --- -- -- -- -- --
-
-- --- -- -- -- -- --
-------
  ------------
  --- -- -- -- -- --
-
-- ------ --- ----- --- ----- ---

$end

Navigates to an empty list at the end of an array. Useful for adding things to the end of a list.

-------
  ------ -------- -- -----
  --- -- --
-
-- --- -- -- -- -- --

$first

Navigates to the first value of an array or object.

---------------- --- -- ---
-- ---

---------------- --- -- -- -- -- ---
-- ---

--------------- --------------- --- -- ---
-- --------- -- --

--------------- --------------- --- -- -- -- -- ---
-- --- -------- -- -- -- --

$last

Navigates to the last value of an array or object.

--------------- --- -- ---
-- ---

--------------- --- -- -- -- -- ---
-- ---

-------------- -------------- --- -- ---
-- --- -- -------

-------------- -------------- --- -- -- -- -- ---
-- --- -- -- -- -- -------

$lens(fn, fromFn)

Lens is like $apply, and the first function behaves identically, in that it transforms the current value using the provided function. But it also takes a second function that can be used to apply the result of a transformation to the current object during an update.

----- ---- - ------
  -- --- ----- -------- ---------- --- ------- ---- ---- -------
  -- ---- ------ ------- ---------- - ------- ----- -- --- ---------- -----------
  - -- - - ----
  -- --- ------ -------- ------- --- -------------- -- --- --- -----
  -- ---- ------ ------- ---------- - ---------- ----- -- --- ------- -----------
  --- -- --- - ---
--

-------
  ----- ----- --- -- --- - --- ---------- -- --- - ----
  --- ----
-
-- --- ----

See custom navigators for more about $lens.

$merge(spec)

Similar to $apply(object => ({...object, ...spec})) except:

  • Does not create a new object if object already has the same keys/values as spec.
  • Will create a new array if object is an array so can be used to merge arrays.
------------------ ----- --- ---
-- --- -- -- --
----------------------- ----- ---- -----
-- ----- ---- ----

$mergeDeep(spec)

Deep merging version of $merge. Typically, deep merging is better handled with nested queries. But if you must...

---------------------- ---- ------ --- ---- --- -- ---
-- --- ---- -- --- --- -- --

$nav(path, ...morePaths)

Given a query path, $nav navigates as if that query was a single selector. This is useful for using queries as navigators (instead of nested queries). This has the same affect as spreading (...) a query into another query.

----- --------- - -------------- --------

-------
  ----------- --------
  -
    ------ -
      ---- -
        ----- -----
      --
      ----- -
        ----- ------
      -
    -
  -
-
-- ------- -------
----- --------- - -------------- --------

-------
  ----------- ------- ----------- -- ---------------------
  -
    ------ -
      ---- -
        ----- -----
      --
      ----- -
        ----- ------
      -
    -
  -
-
-- ------- ----- ------ ------- ----- ------ ---------

You can pass multiple paths to $nav to navigate to all of those paths.

-------
  --------- ------------- ---------- ------- ----------- -- ---------------------
  -
    ------ -
      ---- -
        ----- -----
      --
      ----- -
        ----- ------
      -
    -
  -
-
-- ------- ----- ------ ------- ----- ------ ---------

If path is a function, it will be passed the current object, and it can return a dynamic query. This can be used to inline a custom navigator or just to get the current value in scope.

-------
  -
    ------ -----
      --- -- ----------- ---------- --- -------
    -
  --
  ---- -- -- --- --- -- -- ---
-
-- -
--   --- -- -- -- -------- ------
--   --- -- -- -- -------- ------
-- -

See custom navigators for more about $nav.

$none

Navigates to nothing so you can delete properties from objects and items from arrays. It would be great to use undefined for this, but technically undefined is a value in JS, so $none exists to allow for the edge case of being able to set a property or item to undefined.

-------
  ----- -------
  --- -- -- --
-
-- --- --
-------
  --- -------
  ----- ----
-
-- -----
-------
  ------- ----- -- ----- - - --- -- -------
  --- -- -- -- -- --
-
-- --- -- --

$pick(keys, ...keys)

Navigates to a subset of an object, using the provided keys (or arrays of keys).

-------
  ------------- -------- ------ --------
  ----- ------ ------- ----- ------ -------- ---- ------ -------
-
-- ------- -------
-------
  ----------- ----- -------- -----
  --- -- -- -- -- --
-
-- --- -- -- --
-------
  ----------- ----- ------ ---------- -- --- - ----
  --- -- -- -- -- --
-
--- -- -- -- -- --

$pushContext(key, (obj, context) => contextValue)

This is a convenient form of $setContext where the current value for the key is assumed to be an array and the new value is pushed onto that array. This is especially useful for recursive queries where you want to retain parent context values.

-------
  -
    ---------- -------------------- --------- --
    ---------- -------------------- --------- --
    ---------------- ---- -- ------- --------- ----------
  --
  ------- ----- ---- ---- ----- -------- ----- ---- ---- -----
-
-- -
--   ------ --------- ------- -------- -----
--   ------ --------- ------- -------- -----
--   ------ ----------- ------- -------- -----
--   ------ ----------- ------- -------- ----
-- -

$set(value)

Just a convenience for setting a value, rather than using $apply(() => value). (Also a teensy bit more performant.)

-------
  ------- ---------
  --- -- --
-
-- --- -- --

$setContext(key, (value, context) => contextValue)

Sets a context value for later retrieval in an $apply or $nav.

Context is a way to grab a piece of data at one point in a query and use it at a later point in a query. A good example is grabbing a key from a key/value pair and retrieving that key along with a descendant value so that it can be returned in a selection or used in an update. You could use a $nav function to pull that key into scope, but for multiple levels of nesting, this can get unwieldy. And for recursive queries, this is impossible without creating some kind of complex enveloping mechanism.

$setContext takes a key and a function as arguments. The key is just a way of keeping that piece of context separate from other pieces of context. The function takes the current value and context and returns a value to store for that piece of context. Note that context is never mutated, so the new context only applies to the rest of the query.

-------
  --------------------- -------------- ------ --------------- ---- -- --------------------------
  ----- ---- ----
-
-- ------ ----- -----
-------
  -
    ---------- -------------------- --------- --
    ---------- ------------------ --------- --
    ---------------- ---- -- -------- ---------- ---- -------- ----------
  --
  ------- ----- ---- ---- ----- -------- ----- ---- ---- -----
-
-- -
--   ------- -------- ---- ------ -------- -----
--   ------- -------- ---- ------ -------- -----
--   ------- ---------- ---- ------ -------- -----
--   ------- ---------- ---- ------ -------- ----
-- -
-------
  -
    ------------------------ -------------------- ---------
    ----------
    ------------ ---- -- ------------------------
    ------ ------
  --
  -
    -------- -
      -- ------ -------
      -- ------ ---------
      -- ------ --------
    --
    ---------- ----- ----
  -
-
-- ------- --------

$slice(begin, end)

Navigates to a slice of an array from begin to end index.

-------
  ---------- --- -------
  --- -- -- -- -- --
-
-- --- -- --
-------
  ---------- --- ------ ---------
  --- -- -- -- -- --
-
-- --- -- -- -- -- --
-------
  ---------- --- ----------
  ----- ---- ---- ---- ---- ----
-
-- ----- ---- ---- ----

$traverse({select, update})

See custom navigators.

Custom navigators

Custom navigators are simply composed of other navigators. For example, you could create a $toggle navigator that flips boolean values like this:

----- ------- - ---------- -- ---------------

--------------- --------- ------ -------
-- ------ -----

In particular though, the important navigators for building other navigators are $nav, $lens, and $traverse.

$nav lets you build "path" navigators. A path navigator is the highest level option. If you want to abstract away multiple other navigators, or you want to dynamically choose a navigator based on the current object being navigated to, or you want to do a recursive query, you probably want a path navigator.

$lens lets you build "lens" navigators. A lens navigator is generally going to be simpler than a core navigator and will be almost as performant, but doesn't offer quite as much control. Think of a lens navigator as a two-way $apply. You transform the current object for the rest of the query, and for an update, you define a transformation to be applied to the object. If you always want to call the rest of the query, and you only want to call the rest of the query once, then a lens navigator is a good fit.

$traverse lets you build, for lack of a better term, "core" navigators. It's the lowest-level option and gives you complete control of data selection and updates. Generally, core navigators are going to be the most performant, but they're also going to require the most code. If you need to maybe call the rest of the query, or you want to call the rest of the query many times, you probably need a core navigator.

Path navigators

The simplest path navigator just points to a query path. This is useful for using queries as navigators (instead of nested queries). This has the same affect as spreading (...) a query into another query.

----- --------- - -------------- --------

-------
  ----------- --------
  -
    ------ -
      ---- -
        ----- -----
      --
      ----- -
        ----- ------
      -
    -
  -
-
-- ------- -------

-------
  ----------- ------- ----------- -- ---------------------
  -
    ------ -
      ---- -
        ----- -----
      --
      ----- -
        ----- ------
      -
    -
  -
-
-- ------- ----- ------ ------- ----- ------ ---------

The path passed to $nav can also be a function, which allows it to provide a different path based on the current value.

----- ---------- - -----
  ----- ----- -- -
    -- --------- --- --------- -
      ------ --------- ------ --------
    -
    -- --------- --- ------- -
      ------ ---------
    -
    -- ------- ----------- ----- ----- -----------
  -
--

-------
  ------- ------------
  ------- ------- ----- ------- ------ --------- ------ ------- --------- ------ -------- ----- ---------
-
------- ------

You can also create recursive queries using path navigators.

----- ----- - -----
  ------ ------ --
    ------------------- - ------- ------ - --
--

-------
  ------- --- -- --- - - --- ---
  --- -- -- --- -- -- --- -- ----
-
-- --- -- -- -- --

-------
  ------- --- -- --- - - --- -- ---------- -- --- - -----
  --- -- -- --- -- -- --- -- ----
-
-- --- -- --- --- --- -- ---- -- -----

Parameterized path navigators

To parameterize a path navigator, just create a function that returns a path navigator.

----- ----- - ------- -- -----
  ----- -- -
    -- --------------------- -
      ----- --- ------------ ---- ----- -- ---------
    -
    ------ ---------- --------
  -
--

-------
  ---------- -------
  --- -- -- -- -- --
-
-- --- -- --

-------
  ---------- ------ ---------- -- --- - -----
  --- -- -- -- -- --
-
-- --- --- --- -- --

Lens navigators

Lens navigators are built by using $lens, which takes two functions. The first function applies a transformation for either a select or update. The second function is only invoked for an update and allows you to apply that transformation to the current object.

-- ------ - --------- ---- ------- -- -------- --- ------ -- -- ------
----- ------- - ------
  -------- -- -
    -- ----------------------- -
      ------ --------------
    -
    ----- --- -------------- ---- ----- -- ---------
  --
  ----------- ------- -- -
    ----- --------- - --------------------
    -- ---------- - -------------- -
      ------ --------------- -----------
    -
    -- ---------- - -------------- -
      ------ - ----------------
      --- ---- - - -- - - --------- - -------------- ---- -
        -----------------------
      -
      ------ -------
    -
    ------ -------
  -
--

----------------- --- -- ---
-- ---

-------------- -- --- -- ---
-- --- -- --

-------------- -- --- -- ---
-- --- --

-------------- -- --- -- ---
-- --- -- -- ----------

Parameterized lens navigators

To parameterize a lens navigator, just create a function that returns a lens navigator.

----- ------ - ------ -- ------
  -------- -- -------------------
  ------------- -- ----------------------
--

-------
  ------------- -- ----------- -- ---------------------
  ---------------------
-
-- -------------------

Core navigators

Core navigators are built by using $traverse, which takes an object with a select and update function.

-- ------ - --------- ---- ------- -- -------- --- ------ -- -- ------
----- ------- - -----------
  ------- -------- ----- -- -
    -- ----------------------- -
      ------ --------------------
    -
    ----- --- -------------- ---- ----- -- ---------
  --
  ------- -------- ----- -- -
    -- ----------------------- -
      ----- --------- - --------------------
      -- ---------- - -------------- -
        ------ --------------- -----------
      -
      -- ---------- - -------------- -
        ------ - ----------------
        --- ---- - - -- - - --------- - -------------- ---- -
          -----------------------
        -
        ------ -------
      -
      ------ -------
    -
    ----- --- -------------- ---- ----- -- ---------
  -
---

----------------- --- -- ---
-- ---

-------------- -- --- -- ---
-- --- -- --

-------------- -- --- -- ---
-- --- --

-------------- -- --- -- ---
-- --- -- -- ----------

Parameterized core navigators

To parameterize a core navigator, just create a function that returns a core navigator.

-- ------ - --------- ---- ------- -- ------- --- ----- - ----- -- -- ------
----- ----- - ------- -- -----------
  ------- -------- ----- -- -
    -- ----------------------- -
      ------ -------------------- --------
    -
    ----- --- ------------ ---- ----- -- ---------
  --
  ------- -------- ----- -- -
    -- ----------------------- -
      ----- ------ - -------------------- --------
      ----- -------- - ----------------
      ------------------ ------ -----------
      ------ ---------
    -
    ----- --- ------------ ---- ----- -- ---------
  -
---

------------------ ----- ---- -----
-- ------ -----

--------------- ------ ----- ---- -----
-- ----- ----

Performance

Qim aims to be performant enough.

For Lodash operations that are immutable (like get and set), Qim should have similar performance. Many Lodash functions mutate (like update), and in nearly all cases, Qim will be faster than Lodash/fp's immutable functions. Likewise, Qim will typically be faster than React's immutability helper (now an external package). Ramda seems to perform much better than Lodash/fp, and sometimes it will be faster than Qim, while sometimes Qim will be faster.

In some cases, a custom native helper function using Object.assign or slice along with a mutation may be faster for simple operations, but Qim aims to be as close as possible, while still allowing for a flexible querying API.

Comparing to Immutable.js is difficult in that it heavily depends on your particular use case. The sweet spot for Immutable.js is lots of transformations of large objects or arrays (thousands of items). And even then, you need to avoid marshalling data back and forth between Immutable and plain JS. If you marshal the data back and forth, you'll lose most of the benefit, and if you work with smaller objects and arrays, you're unlikely to see much benefit.

If you want flexible select and update of plain JS objects, Qim is likely to be a good fit. You can check out the current benchmarks to get an idea how Qim stacks up. As with all benchmarks, be careful reading into them too much. Also, Qim is new, and performance tradeoffs could change in favor of simplifying the code or API. Overall, remember that the goal of Qim is to be expressive and performant enough, not to win at every benchmark.

TODO

  • Work with iterables (like Map).
  • More tests.
  • Better error messages.
  • Expose ES6 modules.
  • Static typing? Qim might be diametrically opposed to static types, but worth seeing how good/bad they would fit.

Contributing

  • Do the usual fork and PR thing.
  • Make sure tests pass with npm test and preferaby add additional tests.
  • Make sure to run npm run benchmark to make sure the benchmarks pass. Currently, the benchmarks are running against Node 6.2.2 on a late 2013 MacBook Pro. The benchmarks are by no means perfect, and small regressions may be allowed in exchange for significant improvements, but for the most part, performance needs to remain consistent or improve.
  • Thanks!

Thanks

As mentioned above, the navigator concept borrows heavily from Specter, a Clojure library written by Nathan Marz.

HomePage

https://github.com/jdeal/qim#readme

Repository

git+https://github.com/jdeal/qim.git

来源:JavaScript中文网 ,转载请联系管理员! 本文地址:https://www.javascriptcn.com/post/6005672281e8991b448e3921


猜你喜欢

  • npm 包 aor-language-arabic 使用教程

    介绍 aor-language-arabic 是一个非常实用的 npm 包,它是针对 React 后台管理系统的,支持阿拉伯语言包,对于需要使用阿拉伯语言包的项目非常实用。

    3 年前
  • npm 包 bip32-utils-smart 使用教程

    bip32-utils-smart 是一个前端常用的 JavaScript 库,它提供了一些方便的 API,可以帮助我们进行多种加密、签名等操作。在这篇文章中,我们将详细介绍 bip32-utils-...

    3 年前
  • npm 包 tappo 使用教程

    tappo 是一个用于处理中英文之间添加空格的 npm 包。在中文排版中,应该在中英文之间添加一个空格,以便阅读更加流畅。但是,在写作中,手动添加空格会很繁琐和容易出错。

    3 年前
  • npm 包 graphql-aql-generator 使用教程

    GraphQL 是一种用于 API 的查询语言,可以方便地描述数据的形式。GraphQL 查询是由客户端定义的,完全符合前端的需求。graphql-aql-generator 是一个 npm 包,可以...

    3 年前
  • npm 包 lc-camel-to-hyphen 使用教程

    lc-camel-to-hyphen 是一个非常实用的 NPM 包,用于将驼峰命名法格式的字符串转换为连字符命名法格式的字符串。在前端开发中,我们经常需要将代码中的变量名或者 CSS 样式中的类名从驼...

    3 年前
  • npm 包 q_jade 使用教程

    在前端开发中,使用模板引擎可以提高开发效率,而 q_jade 是一款基于 Jade 的模板引擎。本文将对 q_jade 的使用进行详细介绍。 安装 在命令行中输入以下命令进行安装: --- -----...

    3 年前
  • npm 包 twelite-sdk 使用教程

    Twelite-sdk 是一种用于构建基于低功耗无线传感器网络的 JavaScript 应用程序的 npm 包。 这个包提供了一组工具,能够简化与传感器网络通信、安全、可用性等方面的交互。

    3 年前
  • NPM包Progress-Meter使用教程

    在网页开发中,进度条是一个非常常见的UI元素。而对于一个长时间的任务(比如请求后端数据),进度条可以给用户一个友好的提示,让用户知道网页还在工作,不致于感到卡顿乏味。

    3 年前
  • npm 包 wxbotserv 使用教程

    wxbotserv 是一款强大的 Node.js 开发工具,它可以帮助我们快速构建微信机器人,轻松实现微信公众号应用开发。 安装 wxbotserv 安装 wxbotserv 非常简单,只需要在命令行...

    3 年前
  • npm 包 bitcoin-unocoin-client 使用教程

    简介 bitcoin-unocoin-client 是一个 Node.js 模块,用于与 Unocoin Bitcoin 交易平台进行通信。可以使用该模块的 API 进行连接 Unocoin 并自动执...

    3 年前
  • npm 包 op-tsoa 使用教程

    什么是 op-tsoa? op-tsoa 是一个开源的 Typescript 框架,可以让你更方便地创建 REST API。 这个框架可以生成 OpenAPI、Swagger 和 TypeScript...

    3 年前
  • npm 包 lisectest 使用教程

    简介 lisectest 是一个用于前端单元测试的 npm 包。它能够帮助开发者快速地编写和运行测试代码,以确保代码的正确性。 安装 可以通过以下命令进行安装: --- ------- -------...

    3 年前
  • npm 包 bitcore-lib-chaincoin 使用教程

    在前端开发中,操作区块链数据并且进行加密是非常常见的需求,而 bitcore-lib-chaincoin 就是一个很好的 npm 包来满足这些需求。它是一个轻量级的 JavaScript 客户端工具包...

    3 年前
  • npm 包 react-native-image-gradient 使用教程

    简介 React Native 是一个跨平台的移动应用框架,它基于 JavaScript 和 React 技术栈,可以用来开发 iOS 和 Android 应用。而 react-native-imag...

    3 年前
  • npm 包 datahubjs 使用教程

    在前端开发中,我们常常需要与后台服务器进行数据交互。为了简化这个过程,我们可以使用一些第三方工具或库。其中一个比较好用的库就是 datahubjs。 什么是 datahubjs? datahubjs ...

    3 年前
  • npm 包 dhall-json 使用教程

    在前端开发中,我们需要很多工具和库来辅助我们开发和维护代码。npm 是一个比较常用的工具,它可以帮助我们管理前端项目的依赖项,并且可以方便地安装和升级模块。在 npm 上有一个非常有用的包叫做 dha...

    3 年前
  • npm 包 @discordbuddy/client 使用教程

    @discordbuddy/client 是一款基于 Discord API 的 Node.js 包,用来简化 Discord 机器人的开发过程,提供了很多便捷的工具和方法,方便我们操作 Discor...

    3 年前
  • npm 包 @discordbuddy/compiler 使用教程

    在前端开发中,许多项目都需要进行编译、打包等工作。在这个过程中,我们需要用到各种各样的工具,其中很多工具都可以通过 npm 包来获取。这篇文章将介绍一个非常实用的 npm 包,@discordbudd...

    3 年前
  • npm 包 node-excel-export-gc 使用教程

    npm 是前端工程师必不可少的工具之一,而 node-excel-export-gc,是 npm 上一个非常强大的导出 Excel 的包。下面将会为大家详细讲解如何使用它,并提供示例代码供大家参考。

    3 年前
  • npm 包 @discordbuddy/core 使用教程

    简介 @discordbuddy/core 是一个基于 Node.js 的 Discord 机器人开发库,通过它你可以轻松的创建一个 Discord 机器人应用程序,支持文本交互、语音交互、声音模块等...

    3 年前

相关推荐

    暂无文章