npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@focuson/lens

v1.44.3

Published

A simple implementation of lens using type script

Downloads

8,792

Readme

What is this project?

Immutable data structures are becoming the norm in both front end and server side code. Javascript and typescript have become better at code for handling immutables. Projects such as 'immer' have arisen to help handle this.

This project offers a very simple way to handle one of the most important tasks: accessing deep parts of data structures and perhaps more importantly offering easy ways to mutate them. The code is very small and light weight with no dependancies (other than devDependencies)

Getting started

Tutorials

Links about lenses

  • https://medium.com/@gcanti/introduction-to-optics-lenses-and-prisms-3230e73bfcfe
  • https://medium.com/javascript-scene/lenses-b85976cb0534
  • https://www.linkedin.com/pulse/functional-lenses-javascript-vladim%C3%ADr-gorej/

Example projects

Downloading

You can install this project by

npm install @focuson/lens

Where are the tests?

To keep the projects small, the tests have been moved here

Example usage

We'll start with examining how we work with a deep data structure. Here we can see a block of json representing a dragon. In the dragon's stomach is some hapless adventurer. Our job is to write the eat method which will return a copy of the dragon with the parameter added to the existing contents of the stomach


export let startDragon: Dragon = {
    body: {
        chest: {
            hitpoints: 10,
            stomach: {
                contents: ['the adventurer']
            }
        },
        leftWing: {hitpoints: 5},
        rightWing: {hitpoints: 4}
    },
    head: {hitpoints: 3, leftEye: {color: "blue"}, rightEye: {color: "green"}}
}

Let's write it without Lens first. And once we've written it, let's consider how fragile this code is. Imagine how easy or hard it would be to change if the structure of the dragon changed.


export function eat(dragon: Dragon, item: any): Dragon {
    return {
        ...dragon,
        body: {
            ...dragon.body,
            chest: {
                ...dragon.body.chest,
                stomach: {
                    ...dragon.body.chest.stomach,
                    contents: [...dragon.body.chest.stomach.contents, item]
                }
            }
        }
    }
}

Now let's try writing it with Lenses. And once we've written it, let's consider how easy or hard it would be to change if the structure of the dragon changed

export let dragonContentsL: Lens<Dragon, any[]> = 
    Lenses.build<Dragon>('dragon').focusOn('body').focusOn('chest').
                                       focusOn('stomach').focusOn('contents')
export let eat: (dragon: Dragon, item: any) => 
    dragonContentsL.transform(oldContents => [...oldContents, item])(dragon);

Lenses and focus

The idea behind lenses is that they are 'focused' on a small part of a big object. In the example above the lens dragonContentsl was a lens that given the 'main object' (a Dragon in this case) is focused on just the contents of the stomach. The lens allows us easy access to the thing that is focused on, and allows us to change easily (in the sense of making an immutable copy with the changes) the focused part

In the example above dragonContentsL.transform(oldContents => [...oldContents, item]) creates a function that goes from a dragon to a new dragon. The only difference between the new dragon and the input dragon is the transform function applied to the contents. In otherwords it returns a new dragon, identical except that new one has item added to oldContents

How do they work

A lens has the signature Lens[Main,Child and is two functions. One is a getter of signature (m: Main) => Child. The other is the setter which takes an original main, a new child and returns a new main. (m: Main, c: Child)=> Main.

The magic of composibility

This is easy enough, but the magic comes when we discover that they are composible. It is the composibility that allows the magic of

export let dragonContentsL: Lens<Dragon, any[]> = 
               Lenses.build<Dragon>('dragon').focusOn('body').focusOn('chest').
               focusOn('stomach').focusOn('contents')

Here we started with a lens that was focused on the whole dragon (not the most exciting lens), and then gradually 'focused in' on the place of interest (the contents of the stomach). This works because lenses are composible. If I have a Lens[Main,Child] and a Lens[Child,Grandchild] I can smash them together and create Lens[Main,Grandchild. Intuitively we can just thing 'focuses in on the next part'

Another example can be seen here:

let msgToCupLens = Lens.build<Msg>('msg').focusOn('order').focusOn('cup')
let cupToMadeofLens = Lens.build<Cup>('cup').focusOn('madeOf')
let msgToMadeOfLens = msgToCupLens.andThen(cupToMadeofLens)

How can I use Lens

Changing immutable structures

(With the usual meaning of getting a copy with the difference). The above example shows a simple way to do that

Decoupling the structure from business logic

A good example of this is in the examples code examples/lens/dragon. Here we can see the 'damage' method is handed a lens. The damage code knows nothing about the structure of the dragon, it just knows how to go from a dragon to the hitpoints. This approach of decoupling the business logic from the structure leads to code that keeps on working even when there are dramatic changes to the domain structures

Updating multiple parts simultaneously

Imagine we have a data structure and we need to update two parts at the same time. In the tictactoe example we need to change the state of the current square and the value of next state.

We can do this quite easily by having a lens to 'part1' and a second one to 'part 2', then using the method updateTwoValues or transformTwoValues. The result is a new main with both modifications applied.

Change and lens

It is common for us to want to change the structure of our data. Without lens the impact can be very high: both in the code and in the tests. With lens we can isolate the changes from the business logic, which means that typically we only have to make very few changes when we makes changes to the structure. Without lens if we aren't extremely careful (which may require us to program in a way that isn't idiomatic javascript/typescript) we couple all the business logic to the structure.

If you want to play with the difference and experience it for yourself the dragon example project is a great place to try that. It has a deeper structure than than the coffee example and has many tests. You can do things like 'remove the body structure' and see that the impact using lens is a few lines of code, whereas for the 'without lens' code, the impact is around half the entire code base. This is because lens give us the ability to decouple, and decoupling supports and empowers change.