This started as an indirect port of @ekmett's lens library in PureScript. It has since been broken apart and simplified substantially. The core idea is that these are just van Laarhoven lenses. Read about them as they were called functional references. SPJ gave a brilliant introductory talk about them here. If you're confused, I'd recommend SPJ's talk first. @ekmett has talked about them many times, here's one. For more documentation, you should look at lens and any of the lens resources online.
This is available on bower, so you should be able to just:
bower i purescript-lens
to install it.
You should be able to import just Optic.Lens
and have most of what you need.
See each directory for a summary of the available functions.
There is no TemplateHaskell like syntax so you must define each lens individually. Or make use of purescript-refractor, which has predefined lenses and prisms.
Currently, PureScript doesn't infer constraints #202. If you can fix it, please help out with it.
What this means for you is that you have to annotate your lens/prism/traversal/whatever with a type. This might sound or look hairy, but the types aren't that hard to figure out and it'll go quite a way to show you that there's no magic going on in this library. They're all just type synonyms actually.
There are two main types in this library: Lens
and Prism
.
Both propose a way for "getting" and "setting" values in a data type.
Lens
is for working with product types (Tuple
, records, fields in a data type).
Prism
is for working with sum types (Maybe
, Either
, etc).
Each type proposes some way to look at a specific part of a data type.
With Lens
, it proposes a way to look at one portion of a product type.
E.g. the first field of a tuple, or the foo
field of a record.
With Prism
, it proposes a way to look at one side of a sum type.
E.g. the Left
side of an Either
, or the Nothing
side of a Maybe
.
For almost all of the types provided there are simple versions and more general versions. Using Lens
as the example.
LensP s a
is the simple type when you don't need to change the type of your structure.
Lens s t a b
is the type when you may want to change the type of your structure.
For example:
data Foo = Bar String Number Boolean
fooNum :: LensP Foo Number
fooNum = lens (\(Bar _ n _) -> n) (\(Bar s _ b) n -> Bar s n b)
This type and implementation states that the function fooNum
is a Lens
from the data type Foo
to the Number
field of it.
Since it is a simple type, it does not change the type of the Number
field or change the type of Foo
.
or
foo :: forall b r a t s. Lens {foo :: a | r} {foo :: b | r} a b
foo = lens (\o -> o.foo) (\o x -> o{foo = x})
This type and implementation states that the function foo
is a Lens
from any record with at least a foo
field of type a
to any record with at least a foo
field of type b
.
So, what are the type synonyms? Some examples are:
type Lens s t a b = forall f. (Functor f) => (a -> f b) -> s -> f t
type LensP s a = Lens s s a a
type Prism s t a b = forall f p. (Applicative f, Choice p) => p a (f b) -> p s (f t)
type PrismP s a = Prism s s a a
These might seem scary, especially Prism
, but if you squint at them properly, they look very familiar.
Let's take Lens
, for example, and instantiate s = [a], t = [b]
.
Then we have some type: forall f. (Functor f) => (a -> f b) -> [a] -> f [b]
.
Looks pretty close to map
(from Data.Array
).
In fact, if we instantiate the Functor
with Identity
,
we get the type isomorphic to map
: (a -> Identity b) -> [a] -> Identity [b]
.
With some simple unwrapping, we actually have the type of map
.
What about Prism
?
Let's instantiate the Choice
to (->)
:
type Prism s t a b = forall f. (Applicative f) => (a -> f b) -> s -> f t
This looks pretty close to traverse
.
So, there's a bunch of similarities to the Functor
hierarchy, and that's one of the points of this library.
What these types synonyms allow is the ability to use similar idioms that work in the Functor
hierarchy on containers that are not polymorphic in one variable.
An example of this is if you have some type: data Foo = Foo Number
.
There's no way to define a Functor
instance for Foo
,
so you cannot use (<$>), (<*>), (>>=), (=>>), pure, extract
and friends.
But, it should be easy to see that it would be trivial to "map" over the Number
contained within a Foo
.
If you can define a lens for Foo
, you can do just that
module Foo where
import Optic.Core ((*~), LensP())
data Foo = Foo Number
_Foo :: LensP Foo Number -- forall f. (Functor f) => (Number -> f Number) -> Foo -> f Foo
_Foo f (Foo n) = Foo <$> f n
doubleFoo :: Foo -> Foo
doubleFoo = _Foo *~ 2
Now, this is not necessarily the least verbose option for such a trivial example, but it is definitely one of the more general options. We were able to reuse plain old functions. If we have some deeply nested structure, it is much less verbose for that situation.
N.B. (..)
is (<<<)
purely for aesthetic reasons.
foo..bar..baz..quux
looks better and reads easier than foo<<<bar<<<baz<<<quux
.
Using purescript-refractor for some pre-defined lenses/prisms, we can run this session.
➜ purescript-lens git:(split) psci bower_components/purescript-*/src/**/*.purs
____ ____ _ _
| _ \ _ _ _ __ ___/ ___| ___ _ __(_)_ __ | |_
| |_) | | | | '__/ _ \___ \ / __| '__| | '_ \| __|
| __/| |_| | | | __/___) | (__| | | | |_) | |_
|_| \__,_|_| \___|____/ \___|_| |_| .__/ \__|
|_|
:? shows help
Expressions are terminated using Ctrl+D
> :i Optic.Core
> :i Optic.Refractor.Lens
> :i Data.Tuple
> (Tuple "Hello" "World")^._2
"World"
> (Tuple "Hello" "World") # _2.~42
Tuple ("Hello") (42)
> (Tuple "Hello" (Tuple "World" "!!!")) # _2.._1.~42
Tuple ("Hello") (Tuple (42) ("!!!"))
Please do! Issues, comments, bugs, improvements, whatever.
See the guide first: contibuting.
I am not @ekmett. I don't have the drive to build this entire library by myself. Nor do I have the knowledge to do so. Any help is greatly appreciated.
If you see something wrong, don't hesitate to slap me around and explain why it's wrong.