Repository: yelouafi/focused
Branch: master
Commit: 71973cb893cd
Files: 19
Total size: 47.3 KB
Directory structure:
gitextract_uambnlci/
├── .eslintrc.json
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── index.d.ts
├── package.json
├── src/
│ ├── index.js
│ ├── iso.js
│ ├── lens.js
│ ├── lensProxy.js
│ ├── operations.js
│ ├── prism.js
│ ├── traversal.js
│ ├── typeClasses.js
│ └── utils.js
└── test/
├── curry.test.js
├── index.test.js
└── optics.test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"extends": "eslint:recommended",
"ecmaFeatures": {
"modules": true,
"spread": true,
"restParams": true
},
"env": {
"browser": true,
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module"
},
"rules": {
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
}
}
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
# Node specific modules
cjs/
================================================
FILE: .npmignore
================================================
# dev-oly folders
.babelrc*
.eslintrc*
.bookignore
book.json
test
examples
node_modules
# doc folders
test
docs
examples
_book
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 Yassine Elouafi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# focused
Yet another Optics library for JavaScript, based on the famous lens library from Haskell. Wrapped in a convenient Proxy interface.
Put simply, this library will allow you to:
- Create functional references (Optics), i.e. like pointers to nested parts in data structures (e.g. Object properties, Array elements, Map keys/values, or even fancier parts like a number inside a string ...).
- Apply immutable updates to data structures pointed by those functional references.
# Install
```js
yarn add focused
```
or
```sh
npm install --save focused
```
# Tutorial
Lenses, or Optics in general, are an elegant way, from functional programming, to access and update immutable data structures. Simply put, an optic gives us a reference, also called a _focus_, to a nested part of a data structure. Once we build a focus (using some helper), we can use given functions to access or update, immutably, the embedded value.
In the following tutorial, we'll introduce Optics using `focused` helpers. The library is meant to be friendly for JavaScript developers who are not used to FP jargon.
We'll use the following object as a test bed
```js
import { lensProxy, set, ... } from "focused";
const state = {
name: "Luffy",
level: 4,
nakama: [
{ name: "Zoro", level: 3 },
{ name: "Sanji", level: 3 },
{ name: "Chopper", level: 2 }
]
};
// we'll use this as a convenient way to access deep properties in the state
const _ = lensProxy();
```
## Focusing on a single value
Here is our first example, using the `set` function:
```js
const newState = set(_.name, "Mugiwara", state);
// => { name: "Mugiwara", ... }
```
above, `set` takes 3 arguments:
1. `_.name` is a _Lens_ which lets us focus on the `name` property inside the `state` object
2. The new value which replaces the old one
3. The state to operate on.
It then returns a new state, with the `name` property replaced with the new value.
`over` is like `set` but takes a function instead of a constant value
```js
const newState = over(_.level, x => x * 2, state);
// => { name: "Luffy", level: 8, ... }
```
As you may have noticed, `set` is just a shortcut for `over(lens, _ => newValue, state)`.
Besides properties, we can access elements inside an array
```js
set(_.nakama[0].name, "Jimbi", state);
```
It's important to remember that a Lens focuses _exactly_ on 1 value. no more, no less. In the above example, accessing a non existing property on `state` (or out of bound index) will throw an error.
If you want the access to silently fail, you can prefix the property name with `$`.
```js
const newState = over(_.$assistant.$level, x => x * 2, state);
// newState == state
```
`_.$assistant` is sometimes called an _Affine_, which is a focus on _at most_ one value (ie 0 or 1 value).
There is also a `view` function, which provides a read only access to a Lens
```js
view(_.name, state);
// => Luffy
```
You're probably wondering, what's the utility of the above function, since the access can be trivially achieved with `state.name`. That's true, but Lenses allows more advanced accesses that are not as trivial to achieve as the above case, especially when combined with other Optics as we'll see.
Similarly, `preview` can be used with Affines to safely dereference deeply nested values
```js
preview(_.$assitant.$level, state);
// null
```
## Focusing on multiple values
As we said, Lenses can focus on a single value. To focus on multiple values, we can use the `each` Optic together with `toList` function (`view` can only view a single value).
For example, to gets the `name`s of all Luffy's `nakama`
```js
toList(_.nakama.$(each).name, state);
// => ["Zoro", "Sanji", "Chopper"]
```
Note how we wrapped `each` inside the `.$()` method of the proxy. `.$()` lets us insert arbitrary Optics in the access path which will be automatically composed with the other Optics in the chain.
In Optics jargon, `each` is called a _Traversal_. It's an optic which can focus on multiple parts inside a data structure. Note that Traversals are not restricted to lists. You can create your own Traversals for any _Traversable_ data structure (eg Maps, trees, linked lists ...).
Of course, Traversals work automatically with update functions like `over`. For example
```js
over(_.nakama.$(each).name, s => s.toUpperCase(), state);
```
returns a new state with all `nakama` names uppercased.
Another Traversal is `filtered` which can restrict the focus only to parts meeting some criteria. For example
```js
toList(_.nakama.$(filtered(x => x.level > 2)).name, state);
// => ["Zoro", "Sanji"]
```
retrieves all `nakama`s names with level above `2`. While
```js
over(_.nakama.$(filtered(x => x.level > 2)).name, s => s.toUpperCase(), state);
```
updates all `nakama`s names with level above `2`.
## When the part and the whole matches
Suppose we have the following json
```js
const pkgJson = `{
"name": "my-package",
"version": "1.0.0",
"description": "Simple package",
"main": "index.html",
"scripts": {
"start": "parcel index.html --open",
"build": "parcel build index.html"
},
"dependencies": {
"mydep": "6.0.0"
}
}
`;
```
And we want to focus on the `mydep` field inside `dependencies`. With normal JS code, we can call `JSON.parse` on the json string, modify the field on the created object, then call `JSON.stringify` on the same object to create the new json string.
It turns out that Optics has got a first class concept for the above operations. When the whole (source JSON) and the part (object created by `JSON.parse`) _matches_ we call that an _Isomorphism_ (or simply _Iso_). In the above example we can create an Isomorphism between the JSON string and the corresponding JS object using the `iso` function
```js
const json = iso(JSON.parse, JSON.stringify);
```
`iso` takes 2 functions: one to go from the source to the target, and the other to go back.
> Note this is a partial Optic since `JSON.parse` can fail. We've got another Optic (oh yeah) to account for failure
Ok, so having the `json` Iso, we can use it with the standard functions, for example
```js
set(_.$(json).dependencies.mydep, "6.1.0", pkgJson);
```
returns another JSON string with the `mydep` modified. Abstracting over the parsing/stringifying steps.
The previous example is nice, but it'd be nicer if we can get access to the semver string `6.0.0` as a regular JS object. Let's go a little further and create another Isomorphism for semver like strings
```js
const semver = iso(
s => {
const [major, minor, patch] = s.split(".").map(x => +x);
return { major, minor, patch };
},
({ major, minor, patch }) => [major, minor, patch].join(".")
);
```
Now we can have a focus directly on the parts of a semver string as numbers. Below
```js
over(_.$(json).dependencies.mydep.$(semver).minor, x => x + 1, jsonObj);
```
increments the minor directly in the JSON string.
> Of course, we abstracted over failures in the semver Iso.
## When the match can't always succeed
As I mentioned, the previous case was not a total Isomorphism because JSON strings aren't always parsed to JS objects. So, as you may expect, we need to introduce another fancy name, this time our Optic is called a `Prism`. Which is an Isomorphism that may fail when going from the source to the target (but which always succeeds when going back).
A simple way to create a Prism is the `simplePrism` function. It's like `iso` but you return `null` when the conversion fails.
```js
const maybeJson = simplePrism(s => {
try {
return JSON.parse(s);
} catch (e) {
return null;
}
}, JSON.stringify);
```
So now, something like
```js
const badJSonObj = "@#" + jsonObj;
set(_.$(maybeJson).dependencies.mydep, "6.1.0", badJSonObj);
```
will simply return the original JSON string. The conversion of the `semver` Iso to a Prism is left as an exercise.
# Documentation
Using Optics follows a uniform pattern
- First we create an Optic which focuses on some value(s) inside a container
- Then we use an operation to access or modify the value through the created Optic
>In the following, all showcased functions are imported from the `focused` package
## Creating Optics
As seen in the tutorial,`lensProxy` offers a convenient way to create Optics which focus on javascript objects and arrays. `lensProxy` is essentially a façade API which uses explicit functions behind the scene. In the following examples, we'll see both the proxy and the coresponding explicit functions.
### Object properties
As we saw in the tutorial, we use the familiar property access notation to focus on an object property. For example
```js
const _ = lensProxy()
const nameProp = _.name
```
creates a Lens which focuses on the `name` property of an object.
Using the explicit style, we can use the the `prop` function
```js
const nameProp = prop("name")
```
As said previously, **a Lens focuses exactly on one value**, it means the value must exist in the target container (in this sense the `prop` lens is *partial*). For example, if you use `nameProp` on an object which doesn't have a `name` property, it will throw an error.
### Array elements
As with object properties, we use the array index notation to focus on an array element at a specific index. For example
```js
const _ = lensProxy()
const firstElem = _[0]
```
creates a lens that focuses on the first element on an array. The underlying function is `index`, so we could also write
```js
const firstElem = index(0)
```
`index` is also a partial Lens, meaning it will throw if given index is out of the bounds of the target array.
### Creating custom lenses
The `lens` function can be used to create arbitrary Lenses. The function takes 2 parameters
- `getter` is used to extract the focus value from the target container
- `setter` is used to update the target container with a new focus value.
In the following example, `nameProp` is equivalent to the `nameProp` Lens we saw earlier.
```js
const nameProp = lens(
s => s.name,
(value, s) => ({...s, name: value})
)
```
As you may have guessed, both `prop` and `index` can be implemented using `lens`
### Composing Lenses
>Generally you can combine any 2 Optics together, even if they're of different kind (eg you can combine Lenses with Traversals)
A nice property of Lenses, and Optics in general, is that they can be combined to create a focus on deeply nested values. For example
```js
const _ = lensProxy()
const street = _.freinds[0].address.street
```
creates a Lens which focuses on the `street` of the `address` of the first element of the `freinds` array. As a matter of comparaison, let's say we want to update, immutably, the `street` property on a given object `person`. Using JavaScript spread syntax
```js
const firstFreind = person.freinds[0];
const newPerson = {
...person,
freinds: [
{
...firstFreind,
address: {
...firstFreind.address,
street: "new street"
}
},
...person.freinds.slice(1)
]
};
```
The equivalent operation in `focused` Lenses is
```js
const newPerson = set(_.freinds[0].address.street, "new street", person)
```
We're chaining `.` accesses to successively focus on deeply nested values. Behind the scene, `lensProxy` is creating the necessary `prop` and `index` Lenses, then composing them using `compose` function. Using explicit style, the above Lens could be rewritten like
```js
const streetLens = compose(
prop("freinds"),
index(0),
prop("address"),
prop("street")
);
```
The important thing to remember here, is that`lensProxy` is essentially doing the same thing in the above `compose` example. Plus some memoization tricks to ensure that Lenses are created only once and reused on subsequent operations.
## Creating Isomorphisms
Isomorphisms, or simply Isos, are useful when we want to switch between different representations of the same object. In the tutorial, we already saw `json` which create an Iso between a JSON string and the underlying object that the string parses to.
As we saw, we can use the `iso` function to create a simple Iso. It takes a couple of functions
- the firs function is used to convert from the source representation to the target one
- the second function is used to convert back
We'll see another interesting example of Isos in the next section
## Creating Traversals
While Lenses can focus exactly on one value. Traversals has the ability to focus on many values (including `0`).
### Array Traversals
Perhaps the most familiar Traversal is `each` which focuses on all elements of an array
```js
const todos = ["each", "pray", "love"];
over(each, x => x.toUpperCase(), todos)
// ["EACH", "PRAY", "LOVE"]
```
which is essentially equivalent to the `map` operation of array. However, as we said, what sets Optics apart is their ability to compose with other Optics
```js
const todos = [
{ title: "eat", done: false },
{ title: "pray", done: false },
{ title: "love", done: false }
];
// set done to `true` for all todos
set(
compose(each, prop("done")),
true,
todos
)
```
This can be more concisely formulated using the proxy interface
```js
const _ = lensProxy();
set(_.$(each).done, true, todos)
```
Note that when Traversals are composed with another Optic, the result is always a Traversal.
### Traversing Map's keys/values
Another useful example is traversing keys or values of a JavaScript `Map` object. Although the library already provides `eachMapKey` and `eachMapValue` Traversals for that purpose, it would be instructive to see how we can build them by simple composition of more primitive Optics.
First, we can observe that a `Map` object can be seen also as a collection of `[key, value]` pairs. So we can start by creating an Iso between `Map` and `Array<[key, value]>`
```js
const mapEntries = iso(
map => [...map.entries()],
entries => new Map(entries)
);
```
Then from here, we can traverse keys or values by simply focusing on the appropriate index (`0` or `1`) of each pair in the returned array.
```js
eachMapValue = compose(mapEntries, each, index(1));
eachMapKey = compose(mapEntries, each, index(0));
```
Since composition with a Traversal is also a Traversal. In the above examples, we obtain, in both cases, a Traversal that focuses on all key/values of the Map.
As an illustration, the following example use `eachMapValue` combined with the `prop("score")` lens to increase the score of each player stored in the Map.
```js
const playerMap = new Map([
["Yassine", { name: "Yassine", score: 41 }],
["Yahya", { name: "Yahya", score: 800 }],
["Ayman", { name: "Ayman", score: 410} ]
]);
const _ = lensProxy();
over(
_.$(eachMapValue).score,
x => x + 1000,
playerMap
);
```
### Filtered Traversals
Another useful function is `filtered`. It can be used to restrict the set of values obtained from another Traversal. The function takes 2 arguments
- A predicate used to filter traversed elements
- The Traversal to be filtered (defaults to `each`)
```js
const todos = [
{ title: "eat", done: false },
{ title: "pray", done: true },
{ title: "love", done: true }
];
const isDone = t => t.done
// view title of all done todos
toList(_.$(filtered(isDone)).title, todos);
// => ["pray", "love"]
// set done of all done todos to false
set(_.$(filtered(isDone)).done, false, todos)
```
Note that `filtered` can work with arbitrary traversals, not just arrays.
```js
const playersAbove300 = filtered(p => p.score > 300, eachMapValue)
over(
_.$(playersAbove300).score,
x => x + 1000,
playerMap
);
```
(TBC)
## Todos
- [ ] API docs
- [x] add typings
- [ ] Indexed Traversals
================================================
FILE: index.d.ts
================================================
// convenient shortcut for functions taking 1 param
export type Fn<A, B> = (x: A) => B;
export type Const<R, A> = R & { _brand: A };
export type Either<A, B> =
| { type: "Left"; value: A }
| { type: "Right"; value: B };
export interface Monoid<A> {
empty: () => A;
concat: (xs: A[]) => A;
}
export interface Functor<A, B, FA, FB> {
map(f: Fn<A, B>, x: FA): FB;
}
export interface Applicative<A, B, FA, FB> extends Functor<A, B, FA, FB> {
pure: Fn<B, FB>;
combine: (f: Fn<A[], B>, fas: FA[]) => FB;
}
export interface Getting<R, S, A> {
"~~type~~": "Getting";
"~~apply~~": <FA extends Const<R, A>, FS extends Const<R, S>>(
F: Applicative<A, S, FA, FS>,
f: Fn<A, FA>,
s: S
) => FS;
}
export interface Getter<S, A> {
"~~type~~": "Getting";
"~~apply~~": <R, FA extends Const<R, A>, FS extends Const<R, S>>(
F: Functor<A, S, FA, FS>,
f: Fn<A, FA>,
s: S
) => FS;
}
export interface Iso<S, T, A, B> {
"~~type~~": "Getting" & "Iso" & "Lens" & "Traversal";
"~~apply~~": (<FB, FT>(F: Functor<B, T, FB, FT>, f: Fn<A, FB>, s: S) => FT);
from: (s: S) => A;
to: (b: B) => T;
}
export interface Prism<S, T, A, B> {
"~~type~~": "Getting" & "Prism" & "Traversal";
"~~apply~~": (<FB, FT>(
F: Applicative<B, T, FB, FT>,
f: Fn<A, FB>,
s: S
) => FT);
match: (s: S) => Either<T, A>;
build: (b: B) => T;
}
export interface Lens<S, T, A, B> {
"~~type~~": "Getting" & "Lens" & "Traversal";
"~~apply~~": (<FB, FT>(F: Functor<B, T, FB, FT>, f: Fn<A, FB>, s: S) => FT);
}
export interface Traversal<S, T, A, B> {
"~~type~~": "Getting" & "Traversal";
"~~apply~~": (<FB, FT>(
F: Applicative<B, T, FB, FT>,
f: Fn<A, FB>,
s: S
) => FT);
}
// Monomorphic version
export type SimpleIso<S, A> = Iso<S, S, A, A>;
export type SimplePrism<S, A> = Prism<S, S, A, A>;
export type SimpleLens<S, A> = Lens<S, S, A, A>;
export type SimpleTraversal<S, A> = Traversal<S, S, A, A>;
// arity 2
export function compose<S, T, A, B, C, D>(
parent: Iso<S, T, A, B>,
child: Iso<A, B, C, D>
): Iso<S, T, C, D>;
export function compose<S, T, A, B, C, D>(
parent: Prism<S, T, A, B>,
child: Prism<A, B, C, D>
): Prism<S, T, C, D>;
export function compose<S, T, A, B, C, D>(
parent: Lens<S, T, A, B>,
child: Lens<A, B, C, D>
): Lens<S, T, C, D>;
export function compose<S, T, A, B, C, D>(
parent: Traversal<S, T, A, B>,
child: Traversal<A, B, C, D>
): Traversal<S, T, C, D>;
export function compose<S, A, B>(
parent: Getter<S, A>,
child: Getter<A, B>
): Getter<S, B>;
// arity 3
export function compose<S, T, A, B, C, D, E, F>(
parent: Iso<S, T, A, B>,
child1: Iso<A, B, C, D>,
child2: Iso<C, D, E, F>
): Iso<S, T, E, F>;
export function compose<S, T, A, B, C, D, E, F>(
parent: Prism<S, T, A, B>,
child1: Prism<A, B, C, D>,
child2: Prism<C, D, E, F>
): Prism<S, T, E, F>;
export function compose<S, T, A, B, C, D, E, F>(
parent: Traversal<S, T, A, B>,
child1: Traversal<A, B, C, D>,
child2: Traversal<C, D, E, F>
): Traversal<S, T, E, F>;
export function compose<S, A, B, C>(
parent: Getter<S, A>,
child1: Getter<A, B>,
child2: Getter<B, C>
): Getter<S, C>;
// arity 4
export function compose<S, T, A, B, C, D, E, F, G, H>(
parent: Iso<S, T, A, B>,
child1: Iso<A, B, C, D>,
child2: Iso<C, D, E, F>,
child3: Iso<E, F, G, H>
): Iso<S, T, G, H>;
export function compose<S, T, A, B, C, D, E, F, G, H>(
parent: Prism<S, T, A, B>,
child1: Prism<A, B, C, D>,
child2: Prism<C, D, E, F>,
child3: Prism<E, F, G, H>
): Prism<S, T, G, H>;
export function compose<S, T, A, B, C, D, E, F, G, H>(
parent: Lens<S, T, A, B>,
child1: Lens<A, B, C, D>,
child2: Lens<C, D, E, F>,
child3: Lens<E, F, G, H>
): Lens<S, T, G, H>;
export function compose<S, T, A, B, C, D, E, F, G, H>(
parent: Traversal<S, T, A, B>,
child1: Traversal<A, B, C, D>,
child2: Traversal<C, D, E, F>,
child3: Traversal<E, F, G, H>
): Traversal<S, T, G, H>;
export function compose<S, A, B, C, D>(
parent: Getter<S, A>,
child1: Getter<A, B>,
child2: Getter<B, C>,
child3: Getter<C, D>
): Getter<S, D>;
// arity 5
export function compose<S, T, A, B, C, D, E, F, G, H, I, J>(
parent: Iso<S, T, A, B>,
child1: Iso<A, B, C, D>,
child2: Iso<C, D, E, F>,
child3: Iso<E, F, G, H>,
child4: Iso<G, H, I, J>
): Iso<S, T, I, J>;
export function compose<S, T, A, B, C, D, E, F, G, H, I, J>(
parent: Prism<S, T, A, B>,
child1: Prism<A, B, C, D>,
child2: Prism<C, D, E, F>,
child3: Prism<E, F, G, H>,
child4: Prism<G, H, I, J>
): Prism<S, T, I, J>;
export function compose<S, T, A, B, C, D, E, F, G, H, I, J>(
parent: Lens<S, T, A, B>,
child1: Lens<A, B, C, D>,
child2: Lens<C, D, E, F>,
child3: Lens<E, F, G, H>,
child4: Lens<G, H, I, J>
): Lens<S, T, I, J>;
export function compose<S, T, A, B, C, D, E, F, G, H, I, J>(
parent: Traversal<S, T, A, B>,
child1: Traversal<A, B, C, D>,
child2: Traversal<C, D, E, F>,
child3: Traversal<E, F, G, H>,
child4: Traversal<G, H, I, J>
): Traversal<S, T, I, J>;
export function compose<S, A, B, C, D, E>(
parent: Getter<S, A>,
child1: Getter<A, B>,
child2: Getter<B, C>,
child3: Getter<C, D>,
child4: Getter<D, E>
): Getter<S, E>;
// for higher arities you can use _.$().$()... of nest compose calls
export function over<S, T, A, B>(
optic: Traversal<S, T, A, B>,
updater: (a: A) => B,
state: S
): T;
export function set<S, T, A, B>(
optic: Traversal<S, T, A, B>,
value: B,
state: S
): T;
export function view<S, A>(optic: Getting<A, S, A>, state: S): A;
export function preview<S, A>(
optic: Getting<A | null, S, A>,
state: S
): A | null;
export function has<S, A>(optic: Getting<boolean, S, A>, state: S): boolean;
export function toList<S, A>(optic: Getting<A[], S, A>, state: S): A[];
export function append<S, A>(
optic: SimpleTraversal<S, A[]>,
item: A,
state: S
): S;
export function insertAt<S, A>(
optic: SimpleTraversal<S, A[]>,
index: number,
item: A,
state: S
): S;
export function removeAt<S, A>(
optic: SimpleTraversal<S, A[]>,
index: number,
state: S
): S;
export function removeIf<S, A>(
optic: SimpleTraversal<S, A[]>,
f: (x: A) => boolean,
state: S
): S;
export function iso<S, T, A, B>(
from: (s: S) => A,
to: (b: B) => T
): Iso<S, T, A, B>;
export function from<S, T, A, B>(anIso: Iso<S, T, A, B>): Iso<B, A, T, S>;
export function withIso<S, T, A, B, R>(
anIso: Iso<S, T, A, B>,
f: (from: Fn<S, A>, to: Fn<B, T>) => R
): R;
export function non<A>(x: A): SimpleIso<A | null, A>;
export function anon<A>(x: A, f: Fn<A, boolean>): SimpleIso<A | null, A>;
// TODO: json :: SimpleIso<String,Object>
// TODO: mapEntries :: SimpleIso<Map<Key,Value>>, Array<[Key,Value]>
export function lens<S, T, A, B>(
get: (s: S) => A,
set: (b: B, s: S) => T
): Lens<S, T, A, B>;
export function prop<S, K extends keyof S>(name: K): SimpleLens<S, S[K]>;
export function index<A>(i: number): SimpleLens<[A], A>;
export function atProp<S, K extends keyof S>(
name: K
): SimpleLens<S | null, S[K] | null>;
export function to<S, A>(getter: (s: S) => A): Getter<S, A>;
export function eachOf<A>(): SimpleTraversal<A[], A>;
export const each: SimpleTraversal<any[], any>;
export function filtered<A, B>(
f: (x: A) => Boolean,
traversal?: Traversal<A[], B[], A, B>
): Traversal<A[], B[], A, B>;
export function maybeProp<S, K extends keyof S>(
name: K
): SimpleTraversal<S, S[K]>;
// TODO: eachValue :: SimpleTraversal<Map<K,V>, V>
// TODO: eachKey :: SimpleTraversal<Map<K,V>, K>
export function left<A, B>(a: A): Either<A, B>;
export function rght<A, B>(b: B): Either<A, B>;
export function prism<S, T, A, B>(
match: (s: S) => Either<T, A>,
build: (b: B) => T
): Prism<S, T, A, B>;
export function simplePrism<S, A>(
match: (s: S) => A | null,
build: (a: A) => S
): SimplePrism<S, A>;
export function withPrism<S, T, A, B, R>(
aPrism: Prism<S, T, A, B>,
f: (match: (s: S) => Either<T, A>, build: (b: B) => T) => R
): R;
// TODO: maybeJson :: SimplePrism<String,Object>
export type LensProxy<P, S> = SimpleLens<P, S> &
(S extends object ? { [K in keyof S]: LensProxy<P, S[K]> } : {}) & {
$<A>(child: SimpleLens<S, A>): LensProxy<P, A>;
$<A>(child: SimpleTraversal<S, A>): TraversalProxy<P, A>;
$<A>(child: Getter<S, A>): GetterProxy<P, A>;
};
export type TraversalProxy<P, S> = SimpleTraversal<P, S> &
(S extends object ? { [K in keyof S]: TraversalProxy<P, S[K]> } : {}) & {
$<A>(child: SimpleTraversal<S, A>): TraversalProxy<P, A>;
$<A>(child: Getter<S, A>): GetterProxy<P, A>;
};
export type GetterProxy<P, S> = Getter<P, S> &
(S extends object ? { [K in keyof S]: GetterProxy<P, S[K]> } : {}) & {
$<A>(child: Getter<S, A>): GetterProxy<P, A>;
};
export function lensProxy<P, S = P>(parent?: SimpleLens<P, S>): LensProxy<P, S>;
================================================
FILE: package.json
================================================
{
"name": "focused",
"version": "0.7.2",
"description": "Lens/Optics library for JavaScript",
"module": "src/index.js",
"main": "cjs/index.js",
"typings": "./index.d.ts",
"repository": "https://github.com/yelouafi/focused.git",
"author": "Yassine Elouafi <yelouafi@gmail.com>",
"license": "MIT",
"keywords": [
"optic",
"lens",
"isomorphism",
"prism",
"traversal"
],
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-preset-env": "^1.7.0",
"eslint": "^5.8.0",
"esm": "^3.0.84",
"faucet": "^0.0.1",
"rimraf": "^2.6.2",
"tape": "^4.9.1"
},
"scripts": {
"lint": "eslint src test",
"test": "node -r esm test/index.test | faucet",
"check": "npm run lint && npm run test",
"clean": "rimraf cjs",
"build": "npm run clean && babel src --out-dir cjs",
"prepare": "npm run build",
"prerelease": "npm run check && npm run prepare",
"release:patch": "npm run prerelease && npm version patch && git push --follow-tags && npm publish",
"release:minor": "npm run prerelease && npm version minor && git push --follow-tags && npm publish",
"release:major": "npm run prerelease && npm version major && git push --follow-tags && npm publish"
},
"babel": {
"presets": [
[
"env",
{
"targets": {
"node": "current"
}
}
]
]
}
}
================================================
FILE: src/index.js
================================================
import { curry3, curry4 } from "./utils";
import { over } from "./operations";
export {
iso,
from,
withIso,
non,
anon,
json,
mapEntries,
compose2Isos
} from "./iso";
export { prop, index, lens, atProp } from "./lens";
export {
prism,
withPrism,
simplePrism,
left,
right,
maybeJson,
compose2Prisms
} from "./prism";
export {
each,
eachOf,
filtered,
maybeProp,
eachMapKey,
eachMapValue
} from "./traversal";
export {
view,
preview,
toList,
has,
over,
set,
compose,
compose2
} from "./operations";
export { lensProxy, idLens } from "./lensProxy";
function _append(lens, x, s) {
return over(lens, xs => xs.concat([x]), s);
}
function _insertAt(lens, index, x, s) {
return over(
lens,
xs => {
return xs
.slice(0, index)
.concat([x])
.concat(xs.slice(index));
},
s
);
}
function _removeIf(lens, p, s) {
return over(lens, xs => xs.filter((x, i) => !p(x, i)), s);
}
function _removeAt(lens, index, s) {
return removeIf(lens, (_, i) => i === index, s);
}
export const append = curry3(_append);
export const insertAt = curry4(_insertAt);
export const removeIf = curry3(_removeIf);
export const removeAt = curry3(_removeAt);
================================================
FILE: src/iso.js
================================================
/*
type Iso<S,T,A,B> = (Functor<F>, A => F<B>, S) => F<T> &
{ from: S => A, to: B => T }
type SimpleIso<S,A> = Iso<S,S,A,A>
*/
// iso : (S => A, B => T) => Iso<S,T,A,B>
export function iso(from, to) {
function isoFn(aFunctor, f, s) {
return aFunctor.map(to, f(from(s)));
}
// escape hatch to avoid profunctors
Object.assign(isoFn, {
__IS_ISO: true,
from,
to
});
return isoFn;
}
// withIso : (Iso<S,T,A,B>, (S => A, B => T) => R) => R
export function withIso(anIso, f) {
return f(anIso.from, anIso.to);
}
// parent : Iso<S,T,A,B> from: S => A, to: B => T
// child : Iso<A,B,X,Y> from: A => X, to: Y => B
// return : Iso<S,T,X,Y> from: S => X, to: Y => T
export function compose2Isos(parent, child) {
const { from: sa, to: bt } = parent;
const { from: ax, to: yb } = child;
return iso(s => ax(sa(s)), y => bt(yb(y)));
}
// from : Iso<S,T,A,B> => Iso<B,A,T,S>
export function from(anIso) {
return iso(anIso.to, anIso.from);
}
// non : a => SimpleIso<Maybe<A> without a, A>
export function non(a) {
return iso(
//from : Maybe<A\a> -> a
m => (m === null ? a : m),
//to : a -> Maybe<A\a>
x => (x === a ? null : x)
);
}
// non : (a, A -> boolean) => SimpleIso<Maybe<A> where pred(A) == false, a>
export function anon(a, pred) {
return iso(m => (m === null ? a : m), x => (pred(x) ? null : x));
}
// json : SimpleIso<String,Object>
export const json = iso(JSON.parse, JSON.stringify);
// SimpleIso<Map<Key,Value>>, Array<[Key,Value]>
export const mapEntries = iso(map => [...map.entries()], kvs => new Map(kvs));
================================================
FILE: src/lens.js
================================================
/*
type Lens<S,T,A,B> = (Functor<F>, A => F<B>, S) => F<T>
type SimpleLens<S,A> = Lens<S,S,A,A>
*/
// lens : (S => A, (B,S) => T) => Lens<S,T,A,B>
export function lens(getter, setter) {
return function gsLens(aFunctor, f, s) {
return aFunctor.map(a2 => setter(a2, s), f(getter(s)));
};
}
// prop : K => SimpleLens<S,S[K]>
// PARTIAL : will throw if name isn't a prop of the target
export function prop(name) {
return lens(
s => {
if (!s.hasOwnProperty(name)) {
throw new Error(
`object ${JSON.stringify(s)} doesn't have property ${name}`
);
}
return s[name];
},
(a, s) =>
Object.assign({}, s, {
[name]: a
})
);
}
// index : number => SimpleLens<[A], A>
// PARTIAL : will throw if idx is out of bounds
export function index(idx) {
return function indexLens(aFunctor, f, xs) {
if (idx < 0 || idx >= xs.length) {
throw new Error("index out of bounds!");
}
return aFunctor.map(a2 => {
const ys = xs.slice();
ys[idx] = a2;
return ys;
}, f(xs[idx]));
};
}
// atProp : K => SimpleLens<Maybe<S>, Maybe<S[K]>>
export function atProp(key) {
return function atKeyLens(aFunctor, f, s) {
let a = s !== null && s.hasOwnProperty(key) ? s[key] : null;
return aFunctor.map(a2 => {
if (a2 === null) {
if (a === null || s === null) return s;
const copy = Object.assign({}, s);
delete copy[key];
return copy;
} else {
return Object.assign({}, s, { [key]: a2 });
}
}, f(a));
};
}
// to : (S => A) => SimpleLens<S,A>
export function to(getter) {
return lens(getter, () => {
throw new Error("Can not modify the value of a getter");
});
}
================================================
FILE: src/lensProxy.js
================================================
import { prop, index } from "./lens";
import { maybeProp } from "./traversal";
import { compose2 } from "./operations";
// idLens : SimpleLens<S,S>
export function idLens(_, f, s) {
return f(s);
}
/**
* returns a Proxy object for easing creation & composition of optics
*
* const _ = lensProxy()
* _.name <=> prop("name")
* _.todo.title <=> compose(prop("todo"), prop("title"))
*
* For convenience, safe access to non existing is provided by perfixng the prop name with '$'
*
* _.$name <=> maybeProp("name")
*
* You can also insert other optics usin '$' method of the proxy lensProxy, for example
*
* _.todos.$(each).title <=> compose(prop("todos"), each, prop("title"))
*
* is a traversal that focuses on all titles of the todo array
*/
function getOrCreateLens(memo, parent, key) {
let l = memo.get(key);
if (l == null) {
let child;
const num = Number(key);
if (String(num) === key) {
child = index(num);
} else if (key[0] === "$") {
child = maybeProp(key.slice(1));
} else {
child = prop(key);
}
l = lensProxy(compose2(parent, child));
memo.set(key, l);
}
return l;
}
export function lensProxy(parent = idLens) {
const memo = new Map();
return new Proxy(() => {}, {
get(target, key) {
if (key === "$") {
return child => {
return lensProxy(compose2(parent, child));
};
}
return getOrCreateLens(memo, parent, key);
},
apply(target, thiz, [F, f, s]) {
return parent(F, f, s);
}
});
}
================================================
FILE: src/operations.js
================================================
import { id, konst, curry2, curry3 } from "./utils";
import {
ConstAny,
ConstFirst,
ConstList,
ConstVoid,
Identity,
List
} from "./typeClasses";
import { compose2Isos } from "./iso";
import { compose2Prisms } from "./prism";
/*
type Settable<S,T,A,B> = (Identity, A => Identity<B>, S) => Identity<T>
type Getting<R,S,A> = (Const<R>, A => Const<R,A>, S) => Const<R,S>
type Getter<S,A> = <R>Getting<R,S,A> -- ie should work for any R
*/
// view : Getting<A,S,A> => S => A
export const view = curry2(function _view(aGetter, s) {
return aGetter(ConstVoid, id, s);
});
function _over(aSettable, f, s) {
return aSettable(Identity, f, s);
}
// over : Settable<S,T,A,B> => (A => B) => S => T
export const over = curry3(_over);
// set : Settable<S,T,A,B> => B => S => T
export const set = curry3(function _set(aSettable, v, s) {
return _over(aSettable, konst(v), s);
});
// toList : Getting<[A], S,A> => S => [A]
export const toList = curry2(function toList(aGetting, s) {
return aGetting(ConstList, List.pure, s);
});
// preview : Getting<A | null, S,A> => S => (A | null)
export const preview = curry2(function _preview(aGetting, s) {
return aGetting(ConstFirst, id, s);
});
// has : (Getting<Boolean, S,A>, S) => Boolean
export const has = curry2(function _has(aGetting, s) {
return aGetting(ConstAny, konst(true), s);
});
/**
* Compose 2 optics, Abstarcting the constraints, the type can be seen as
*
* compose2 : (Optic<S,T,A,B>, Optic<A,B,X,Y>) => Optic<S,T,A,B>
*
* However, we need also to combine 2 Isos into an Iso and idem for Prisms
* In Haskell this is acheived using type classes & Profunctors
*
* Here we're just inspecting types at runtime, it's ugly and less flexible but
* works for our limited cases. Most notably, I don't want to introduce Profunctors
* for performance reasons.
*/
export function compose2(parent, child) {
// ad-hoc polymporphism FTW
if (parent.__IS_ISO && child.__IS_ISO) {
return compose2Isos(parent, child);
}
if (parent.__IS_PRISM && child.__IS_PRISM) {
return compose2Prisms(parent, child);
}
return function composedOptics(F, f, s) {
return parent(F, a => child(F, f, a), s);
};
}
export function compose(...ls) {
return ls.reduce(compose2);
}
================================================
FILE: src/prism.js
================================================
/*
type Either<T,A> = { type: "LEFT", value: T } | { type: "RIGHT", value: A }
type Prism<S,T,A,B> =
(Applicative<F>, A => F<B>, S) => F<T>
& { __IS_PRISM: true, match: S => Either<T,A>, to: B => T }
type SimplePrism<S,A> = Prism<S,S,A,A>
*/
export function left(value) {
return { type: "LEFT", value };
}
export function right(value) {
return { type: "RIGHT", value };
}
// prism : (S => Either<T,A>, B => T) => Prism<S,T,A,B>
export function prism(match, build) {
function prismFn(anApplicative, f, s) {
const result = match(s);
if (result.type === "LEFT") return anApplicative.pure(result.value);
const fa = f(result.value);
return anApplicative.map(build, fa);
}
// escape hatch to avoid profunctors
Object.assign(prismFn, {
__IS_PRISM: true,
build,
match
});
return prismFn;
}
// simplePrism : (S => Maybe<A>, A => S) => SimplePrism<S,A>
export function simplePrism(match, build) {
return prism(s => {
const result = match(s);
return result === null
? { type: "LEFT", value: s }
: { type: "RIGHT", value: result };
}, build);
}
// withPrism : (Prism<S,T,A,B>, (S => Either<T,A>, B => T) => R) => R
export function withPrism(aPrism, f) {
return f(aPrism.match, aPrism.build);
}
// parent : Prism<S,T,A,B> & { match: S => Either<T,A>, to: B => T }
// child : Prism<A,B,X,Y> & { match: A => Either<B,X>, to: Y => B }
// return : Prism<S,T,X,Y> & { match: S => Either<T,X>, to: Y => T }
export function compose2Prisms(parentL, childL) {
const { match: sta, build: bt } = parentL;
const { match: abx, build: yb } = childL;
return prism(
s => {
const ta = sta(s);
if (ta.type === "LEFT") return ta;
const bx = abx(ta.value);
if (bx.type === "RIGHT") return bx;
return bt(bx.value);
},
y => bt(yb(y))
);
}
// json : SimplePrism<String,Object>
export const maybeJson = simplePrism(s => {
try {
return JSON.parse(s);
} catch (e) {
return null;
}
}, JSON.stringify);
================================================
FILE: src/traversal.js
================================================
import { id } from "./utils";
import { compose } from "./operations";
import { mapEntries } from "./iso";
import { index } from "./lens";
/*
type Traversal<S,T,A,B> = (Applicative<F>, A => F<B>, S) => F<T>
type SimpleTraversal<S,A> = Traversal<S,S,A,A>
*/
// each : Traversal< Array<A>, Array<B>, A, B>
export function each(anApplicative, f, xs) {
return anApplicative.combine(id, xs.map(f));
}
// eachOf: <A>() => Traversal< Array<A>, Array<B>, A, B>
// this of the convenience of typings since TypeScript doesn't
// allow type parameters on non functions
export function eachOf() {
return each;
}
// filter : (A => Boolean) => Traversal< Array<A>, Array<B>, A, B>
export function filtered(pred, traverse = each) {
return function filterTraversal(anApplicative, f, s) {
return traverse(anApplicative, update, s);
function update(v) {
return pred(v) ? f(v) : anApplicative.pure(v);
}
};
}
// maybeProp :: K => SimpleTraversal<S, S[K]>
// This is an Affine Traversal; ie focus on 0 or 1 value
export function maybeProp(name) {
return function propTraversal(anApplicative, f, s) {
if (!s.hasOwnProperty(name)) {
return anApplicative.pure(s);
}
return anApplicative.map(a2 => {
return Object.assign({}, s, {
[name]: a2
});
}, f(s[name]));
};
}
// eachValue :: SimpleTraversal<Map<K,V>, V>
export const eachMapValue = compose(
mapEntries,
each,
index(1)
);
// eachKey :: SimpleTraversal<Map<K,V>, K>
export const eachMapKey = compose(
mapEntries,
each,
index(0)
);
================================================
FILE: src/typeClasses.js
================================================
export const Void = {
empty: () => {
throw "Void.empty! (you're likely using view with a Traversal, try preview or toList instead)";
},
concat: () => {
throw "Void.concat! (you're likely using view with a Traversal, try preview or toList instead)";
}
};
export const List = {
empty: () => [],
concat: xxs => [].concat(...xxs),
pure: x => [x],
map: (f, xs) => xs.map(f)
};
export const First = {
empty: () => null,
concat2: (x1, x2) => (x1 !== null ? x1 : x2),
concat: xs => xs.reduce(First.concat2, null)
};
export const Any = {
empty: () => false,
concat2: (x1, x2) => x1 || x2,
concat: xs => xs.reduce(Any.concat2, false)
};
export const Identity = {
pure: x => x,
map: (f, x) => f(x),
combine: (f, xs) => f(xs)
};
export const Const = aMonoid => ({
pure: _ => aMonoid.empty(),
map: (f, x) => x,
combine: (_, xs) => aMonoid.concat(xs)
});
export const ConstVoid = Const(Void);
export const ConstList = Const(List);
export const ConstFirst = Const(First);
export const ConstAny = Const(Any);
================================================
FILE: src/utils.js
================================================
export const id = x => x;
export const konst = x => _ => x;
export function curry2(f) {
return function curried2(x, y) {
if (arguments.length >= 2) return f(x, y);
return function curried2_1arg(y) {
return f(x, y);
};
};
}
export function curry3(f) {
return function curried3(x, y, z) {
if (arguments.length >= 3) return f(x, y, z);
if (arguments.length === 2) {
return function curried3_2args(z) {
return f(x, y, z);
};
}
return curry2(function curried3_1(y, z) {
return f(x, y, z);
});
};
}
export function curry4(f) {
return function curried4(w, x, y, z) {
if (arguments.length >= 4) return f(w, x, y, z);
if (arguments.length === 3) {
return function curried4_3args(z) {
return f(w, x, y, z);
};
}
if (arguments.length === 2) {
return curry2(function curried4_2args(y, z) {
return f(w, x, y, z);
});
}
return curry3(function curried4_1(x, y, z) {
return f(w, x, y, z);
});
};
}
================================================
FILE: test/curry.test.js
================================================
import test from "tape";
import { curry2, curry3, curry4 } from "../src/utils";
const add2 = curry2((x, y) => x + y);
const add3 = curry3((x, y, z) => x + y + z);
const add4 = curry4((w, x, y, z) => w + x + y + z);
test("curry2", assert => {
assert.equal(add2(1, 2), 3);
assert.equal(add2(1)(2), 3);
assert.end();
});
test("curry3", assert => {
assert.equal(add3(1, 2, 3), 6);
assert.equal(add3(1, 2)(3), 6);
assert.equal(add3(1)(2, 3), 6);
assert.equal(add3(1)(2)(3), 6);
assert.end();
});
test("curry4", assert => {
assert.equal(add4(1, 2, 3, 4), 10);
assert.equal(add4(1, 2, 3)(4), 10);
assert.equal(add4(1, 2)(3, 4), 10);
assert.equal(add4(1, 2)(3)(4), 10);
assert.equal(add4(1)(2, 3, 4), 10);
assert.equal(add4(1)(2, 3)(4), 10);
assert.equal(add4(1)(2)(3, 4), 10);
assert.equal(add4(1)(2)(3)(4), 10);
assert.end();
});
================================================
FILE: test/index.test.js
================================================
import "./curry.test";
import "./optics.test";
================================================
FILE: test/optics.test.js
================================================
import test from "tape";
import {
iso,
maybeJson,
view,
over,
toList,
preview,
set,
each,
filtered,
json,
lensProxy,
anon,
atProp,
compose,
append,
insertAt,
removeIf,
removeAt
} from "../src";
const state = {
name: "Luffy",
level: 4,
nakama: [
{ name: "Zorro", level: 3 },
{ name: "Sanji", level: 3 },
{ name: "Chooper", level: 2 }
]
};
const _ = lensProxy();
test("view/prop", assert => {
assert.equal(view(_.name, state), "Luffy");
assert.end();
});
test("preview/maybeProp", assert => {
assert.deepEqual(preview(_.$lastname.level, state), null);
assert.end();
});
test("over/maybeProp", assert => {
assert.deepEqual(over(_.$assitant.$level, x => x * 2, state), state);
assert.end();
});
test("over/prop", assert => {
assert.deepEqual(over(_.level, x => x * 2, state), {
...state,
level: state.level * 2
});
assert.end();
});
test("view/atProp", assert => {
assert.equal(
view(atProp("surname"), { name: "Luffy" }),
null,
"should return null if property is absent"
);
assert.end();
});
test("view/atProp", assert => {
assert.deepEqual(
set(atProp("surname"), "Monkey D.", { name: "Luffy" }),
{
name: "Luffy",
surname: "Monkey D."
},
"Should add the property if absent"
);
assert.deepEqual(
set(
compose(
atProp("navigator"),
atProp("name")
),
"Nami",
{ name: "Luffy" }
),
{
name: "Luffy",
navigator: { name: "Nami" }
},
"Should add deeply nested property if absent"
);
assert.end();
});
// _.at('address').at('street')
// Lens<{}, Maybe<{}>> . Lens<{}, Maybe<String>>
test("toList/each", assert => {
assert.deepEqual(
toList(_.nakama.$(each).name, state),
state.nakama.map(n => n.name)
);
assert.end();
});
test("toList/filtered", assert => {
assert.deepEqual(
toList(_.nakama.$(filtered(x => x.level > 2)).name, state),
state.nakama.filter(n => n.level > 2).map(n => n.name)
);
assert.end();
});
test("over/filtered", assert => {
assert.deepEqual(
over(_.nakama.$(filtered(x => x.level > 2)).name, s => `**${s}**`, state),
{
...state,
nakama: state.nakama.map(n =>
n.level > 2 ? { ...n, name: `**${n.name}**` } : n
)
}
);
assert.end();
});
test("set/[0]", assert => {
assert.deepEqual(set(_.nakama[0].name, "Jimbi", state), {
...state,
nakama: state.nakama.map((n, i) => (i === 0 ? { ...n, name: "Jimbi" } : n))
});
assert.end();
});
test("iso/anon", assert => {
// nonZero is an Iso from Maybe<negative number> to number (with 1 as default value)
// source is either null or a negative number
// target is any number
const negative = anon(0, x => x >= 0);
assert.equal(view(negative, -10), -10);
assert.equal(view(negative, null), 0);
assert.equal(
set(negative, -3, -10),
-3,
"should allow setting to negative numbers"
);
assert.equal(
set(negative, -3, null),
-3,
"should allow setting on null values"
);
assert.equal(
set(negative, 10, -3),
null,
"should not allow setting non-negative numbers"
);
assert.end();
});
const jsonObj = `{
"name": "my-package",
"version": "1.0.0",
"description": "Simple package",
"main": "index.html",
"scripts": {
"start": "parcel index.html --open",
"build": "parcel build index.html"
},
"dependencies": {
"mydep": "6.0.0"
}
}
`;
const semver = iso(
s => {
const [major, minor, patch] = s.split(".").map(x => +x);
return { major, minor, patch };
},
({ major, minor, patch }) => [major, minor, patch].join(".")
);
test("over/iso", assert => {
const actualJSON = over(
_.$(json).dependencies.mydep.$(semver).minor,
x => x + 1,
jsonObj
);
const js = JSON.parse(jsonObj);
js.dependencies.mydep = "6.1.0";
assert.deepEqual(JSON.parse(actualJSON), js);
assert.end();
});
test("over/prism", assert => {
const badJSonObj = "@#" + jsonObj;
assert.equal(
set(_.$(maybeJson).dependencies.mydep, "6.1.0", badJSonObj),
badJSonObj
);
assert.end();
});
test("append", assert => {
assert.deepEqual(append(_.nakama, { name: "Nami", level: 1 }, state), {
...state,
nakama: state.nakama.concat({ name: "Nami", level: 1 })
});
assert.end();
});
test("insertAt", assert => {
assert.deepEqual(insertAt(_.nakama, 1, { name: "Nami", level: 1 }, state), {
...state,
nakama: [
state.nakama[0],
{ name: "Nami", level: 1 },
...state.nakama.slice(1)
]
});
assert.end();
});
test("removeIf", assert => {
assert.deepEqual(removeIf(_.nakama, n => n.level > 2, state), {
...state,
nakama: state.nakama.filter(n => n.level <= 2)
});
assert.end();
});
test("removeAt", assert => {
assert.deepEqual(removeAt(_.nakama, 2, state), {
...state,
nakama: state.nakama.filter((_, i) => i !== 2)
});
assert.end();
});
gitextract_uambnlci/
├── .eslintrc.json
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── index.d.ts
├── package.json
├── src/
│ ├── index.js
│ ├── iso.js
│ ├── lens.js
│ ├── lensProxy.js
│ ├── operations.js
│ ├── prism.js
│ ├── traversal.js
│ ├── typeClasses.js
│ └── utils.js
└── test/
├── curry.test.js
├── index.test.js
└── optics.test.js
SYMBOL INDEX (53 symbols across 9 files)
FILE: index.d.ts
type Fn (line 2) | type Fn<A, B> = (x: A) => B;
type Const (line 4) | type Const<R, A> = R & { _brand: A };
type Either (line 6) | type Either<A, B> =
type Monoid (line 10) | interface Monoid<A> {
type Functor (line 15) | interface Functor<A, B, FA, FB> {
type Applicative (line 19) | interface Applicative<A, B, FA, FB> extends Functor<A, B, FA, FB> {
type Getting (line 24) | interface Getting<R, S, A> {
type Getter (line 33) | interface Getter<S, A> {
type Iso (line 42) | interface Iso<S, T, A, B> {
type Prism (line 49) | interface Prism<S, T, A, B> {
type Lens (line 60) | interface Lens<S, T, A, B> {
type Traversal (line 65) | interface Traversal<S, T, A, B> {
type SimpleIso (line 75) | type SimpleIso<S, A> = Iso<S, S, A, A>;
type SimplePrism (line 76) | type SimplePrism<S, A> = Prism<S, S, A, A>;
type SimpleLens (line 77) | type SimpleLens<S, A> = Lens<S, S, A, A>;
type SimpleTraversal (line 78) | type SimpleTraversal<S, A> = Traversal<S, S, A, A>;
type LensProxy (line 290) | type LensProxy<P, S> = SimpleLens<P, S> &
type TraversalProxy (line 297) | type TraversalProxy<P, S> = SimpleTraversal<P, S> &
type GetterProxy (line 303) | type GetterProxy<P, S> = Getter<P, S> &
FILE: src/index.js
function _append (line 43) | function _append(lens, x, s) {
function _insertAt (line 47) | function _insertAt(lens, index, x, s) {
function _removeIf (line 60) | function _removeIf(lens, p, s) {
function _removeAt (line 64) | function _removeAt(lens, index, s) {
FILE: src/iso.js
function iso (line 9) | function iso(from, to) {
function withIso (line 23) | function withIso(anIso, f) {
function compose2Isos (line 30) | function compose2Isos(parent, child) {
function from (line 37) | function from(anIso) {
function non (line 42) | function non(a) {
function anon (line 52) | function anon(a, pred) {
FILE: src/lens.js
function lens (line 7) | function lens(getter, setter) {
function prop (line 15) | function prop(name) {
function index (line 34) | function index(idx) {
function atProp (line 48) | function atProp(key) {
function to (line 65) | function to(getter) {
FILE: src/lensProxy.js
function idLens (line 6) | function idLens(_, f, s) {
function getOrCreateLens (line 28) | function getOrCreateLens(memo, parent, key) {
function lensProxy (line 46) | function lensProxy(parent = idLens) {
FILE: src/operations.js
function _over (line 24) | function _over(aSettable, f, s) {
function compose2 (line 63) | function compose2(parent, child) {
function compose (line 76) | function compose(...ls) {
FILE: src/prism.js
function left (line 11) | function left(value) {
function right (line 15) | function right(value) {
function prism (line 20) | function prism(match, build) {
function simplePrism (line 37) | function simplePrism(match, build) {
function withPrism (line 47) | function withPrism(aPrism, f) {
function compose2Prisms (line 54) | function compose2Prisms(parentL, childL) {
FILE: src/traversal.js
function each (line 11) | function each(anApplicative, f, xs) {
function eachOf (line 18) | function eachOf() {
function filtered (line 23) | function filtered(pred, traverse = each) {
function maybeProp (line 35) | function maybeProp(name) {
FILE: src/utils.js
function curry2 (line 4) | function curry2(f) {
function curry3 (line 13) | function curry3(f) {
function curry4 (line 27) | function curry4(f) {
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (52K chars).
[
{
"path": ".eslintrc.json",
"chars": 354,
"preview": "{\n \"extends\": \"eslint:recommended\",\n \"ecmaFeatures\": {\n \"modules\": true,\n \"spread\": true,\n \"restParams\": true"
},
{
"path": ".gitignore",
"chars": 944,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directo"
},
{
"path": ".npmignore",
"chars": 127,
"preview": "# dev-oly folders\n.babelrc*\n.eslintrc*\n.bookignore\nbook.json\ntest\nexamples\nnode_modules\n\n# doc folders\ntest\ndocs\nexample"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2018 Yassine Elouafi\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 15667,
"preview": "\n# focused\n\nYet another Optics library for JavaScript, based on the famous lens library from Haskell. Wrapped in a conve"
},
{
"path": "index.d.ts",
"chars": 8821,
"preview": "// convenient shortcut for functions taking 1 param\nexport type Fn<A, B> = (x: A) => B;\n\nexport type Const<R, A> = R & {"
},
{
"path": "package.json",
"chars": 1403,
"preview": "{\n \"name\": \"focused\",\n \"version\": \"0.7.2\",\n \"description\": \"Lens/Optics library for JavaScript\",\n \"module\": \"src/ind"
},
{
"path": "src/index.js",
"chars": 1233,
"preview": "import { curry3, curry4 } from \"./utils\";\nimport { over } from \"./operations\";\nexport {\n iso,\n from,\n withIso,\n non,"
},
{
"path": "src/iso.js",
"chars": 1631,
"preview": "/*\n type Iso<S,T,A,B> = (Functor<F>, A => F<B>, S) => F<T> & \n { from: S => A, to: B => T }"
},
{
"path": "src/lens.js",
"chars": 1742,
"preview": "/*\n type Lens<S,T,A,B> = (Functor<F>, A => F<B>, S) => F<T>\n type SimpleLens<S,A> = Lens<S,S,A,A>\n*/\n\n// lens : (S"
},
{
"path": "src/lensProxy.js",
"chars": 1580,
"preview": "import { prop, index } from \"./lens\";\nimport { maybeProp } from \"./traversal\";\nimport { compose2 } from \"./operations\";\n"
},
{
"path": "src/operations.js",
"chars": 2274,
"preview": "import { id, konst, curry2, curry3 } from \"./utils\";\nimport {\n ConstAny,\n ConstFirst,\n ConstList,\n ConstVoid,\n Iden"
},
{
"path": "src/prism.js",
"chars": 2051,
"preview": "/*\n type Either<T,A> = { type: \"LEFT\", value: T } | { type: \"RIGHT\", value: A }\n\n type Prism<S,T,A,B> = \n "
},
{
"path": "src/traversal.js",
"chars": 1560,
"preview": "import { id } from \"./utils\";\nimport { compose } from \"./operations\";\nimport { mapEntries } from \"./iso\";\nimport { index"
},
{
"path": "src/typeClasses.js",
"chars": 1047,
"preview": "export const Void = {\n empty: () => {\n throw \"Void.empty! (you're likely using view with a Traversal, try preview or"
},
{
"path": "src/utils.js",
"chars": 1035,
"preview": "export const id = x => x;\nexport const konst = x => _ => x;\n\nexport function curry2(f) {\n return function curried2(x, y"
},
{
"path": "test/curry.test.js",
"chars": 863,
"preview": "import test from \"tape\";\nimport { curry2, curry3, curry4 } from \"../src/utils\";\n\nconst add2 = curry2((x, y) => x + y);\nc"
},
{
"path": "test/index.test.js",
"chars": 47,
"preview": "import \"./curry.test\";\nimport \"./optics.test\";\n"
},
{
"path": "test/optics.test.js",
"chars": 4964,
"preview": "import test from \"tape\";\nimport {\n iso,\n maybeJson,\n view,\n over,\n toList,\n preview,\n set,\n each,\n filtered,\n "
}
]
About this extraction
This page contains the full source code of the yelouafi/focused GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (47.3 KB), approximately 14.9k tokens, and a symbol index with 53 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.