Profile picture
Yassine Elouafi @YassineElouafi2
, 16 tweets, 2 min read Read on Twitter
Wanted to share a thing about my optics lib "focused". One could assume that code like

_.person.freinds[0].name

Hides some complicated dark magic but in reality it's really simple. It all boils down to function composition, let me explain...
To start, An optic could be seen as simply a function of the form

(A => A) => (S => S)

It takes a function acting on a 'thing' (A) and turns it into a function acting on a 'bigger thing' (S)
So a lens that focuses on the 'name' prop of a 'Person' object is like a

(String => String) => (Person => Person)
The nice thing is that those functions compose naturally. If I have

(Person => Person) => (Contact => Contact)
and

(String => String) => (Person => Person)

The 2 functions compose to give a

(String => String) => (Contact => Contact)
And you can keep composing those functions to focus on arbitrarly deeper levels. That's what makes the concept really elegant
Now let's add some fancy wrapper around the results. Also there's no reason holding us from changing result types

(A => F<B>) (S => F<T>)

They are still composables. But now we get additional power by the 'mapper' F. Here is where the intersting stuff comes in
For example, by choosing F to be a Functor, we obtain a 'Lens' which can focus only on 1 value, by choosing F to be an Applicative Functor, we obtain a Traversal which can focus on multiple values
The neat thing is that different optics (lenses, traversals, prisms, isos) can still be composed and the result is automatically defined by how the fancy wrappers unify
For example composing a lens with a traversal we get a traversal because Applicative is 'like a subclass' of Functor so the composition logically yields the more restrictive type (eg in focused lib _.freinds. $(each).name is a traversal that returns the names of all freinds)
More interesting by choosing a concrete type you can obtain different behaviors. eg. by passing the 'Identity' functor to the function you can set the value inside. Passing the 'Const' functor you can just read the value inside.
So you dont have to do some complex case analysis to write composition or getting/setting code. It all follows from the function representation
In 'focused' code like _.freinds. $(each).name is just using a proxy that intercepts 'get' calls, creates a property lens and composes it with the parent optic, that's all.
I dont know about you but I really find the whole concept really beautiful. Things like this from FP world feels like you're not 'building your model' but you're just discovering it by 'folllowing the types'
But wait, there's more to it, functions are just a special case of the more general idea of "black boxes" that transform some input A into output B you can also generalise the signature to

P<A,B> => P<S,T>

The fancy wrapper P is called a Profunctor!
Which opens the doors to a more beatiful world. But this is another story.
And just to be clear I didn't invent the above ideas. They where 'discovered' by haskell devs many yesrs ago, I just hacked/adapted the design to JavaScript
Missing some Tweet in this thread?
You can try to force a refresh.

Like this thread? Get email updates or save it to PDF!

Subscribe to Yassine Elouafi
Profile picture

Get real-time email alerts when new unrolls are available from this author!

This content may be removed anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member and get exclusive features!

Premium member ($30.00/year)

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!