Giter Site home page Giter Site logo

optics's Introduction

optics

Build Status Hackage Dependencies Stackage LTS Stackage Nightly

The optics family of Haskell packages make it possible to define and use Lenses, Traversals, Prisms and other optics, using an abstract interface. They are roughly comparable in functionality with the lens package, but explore a different part of the design space. For a detailed introduction, see the Haddocks for the main Optics module.

Authors and contributors

The authors of the optics family of packages are:

  • Adam Gundry
  • Andres Löh
  • Andrzej Rybczak
  • Oleg Grenrus

Our thanks go to those who have (involuntarily) contributed code and ideas to optics. In particular, we have liberally reused parts of the lens package by Edward Kmett and contributors.

Package structure

Officially supported packages

  • optics is a "batteries-included" package with many dependencies. It incorporates:

    • optics-core: core definitions with a minimal dependency footprint.

    • optics-extra: extra definitions and instances that extend optics-core, incurring dependencies on various boot library packages.

    • optics-th: machinery to construct optics using TemplateHaskell.

    • indexed-profunctors: internal definitions of indexed profunctor representation.

  • optics-vl: utilities for compatibility with van Laarhoven isomorphisms and prisms, as defined in the lens library. This package is not included in optics as it imposes a dependency on profunctors. Note that optics-core already supports conversion for van Laarhoven lenses and various other optics.

  • template-haskell-optics: optics for working with types in the template-haskell package (see optics-th for using TemplateHaskell to construct optics).

Work in progress packages

These packages have not (yet) been officially released. If you find them useful, we would welcome offers to maintain these packages.

  • optics-sop: generic construction of optics using the generics-sop package, and optics for generics-sop types.

Internal packages

These packages are for internal use only, and are not intended to be released:

  • metametapost: generates diagrams used in the documentation, and an example of using optics.

  • optics-codegen: code generator for the Is class and Join type family used internally by optics.

optics's People

Contributors

adamgundry avatar arybczak avatar avi-d-coder avatar bwignall avatar evanrelf avatar felixonmars avatar georgefst avatar jhrcek avatar kosmikus avatar matobet avatar patrickt avatar phadej avatar ryanglscott avatar skykanin avatar tanyabouman avatar tomjaguarpaw avatar treeowl avatar vekhir avatar ysangkok avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

optics's Issues

Ticked vs unticked optics tags

Let's choose one. If we change representation of indices to a type level list, clients of the library will need DataKinds anyway, so we can remove type aliases then.

Expose all internal modules

I remembered how annoying hidden internals are and why I always export everything in my projects.

  1. We kinda have to as we need access to internals from our own subpackages that use optics-core.

  2. Hiding internals is annoying for the user, especially if the library is fresh and potentially missing features. If someone uses optics and finds out that it's missing some sort of feature he used to have in lens (or he didn't, but he wants to add it anyway), with all modules exposed he can just import a few internal modules, write stuff locally in his project and have the problem solved on the spot (he can also make a PR later). If internals are not exposed, he needs to jump through many hoops, i.e. download source code of optics, modify appropriate subpackage, have local version configured to work with his project, potentially need to upload it to external hackage if it has to be used e.g. for a commercial project, then make the PR and hope that it'll get merged soon and he won't have to maintain the fork. In short, terrible.

Obviously no guarantees are offered by importing any Internal modules, but that's fine as long as these are not provided by the top level module that's supposed to be imported for usual use.

Coindexed optics?

Is there anything stopping us adding coindexed optics, apart from wanting to keep down the number of type variables? Perhaps there are tricks we can play here, e.g. see #19 (comment).

Drop support for GHC 8.0.2

Advantages:

  • no CPP for MIN_VERSION_containers(0,5,8) (we have a few of those, a couple more will come at least with Optics.At)
  • we can have both :: Bitraversable r => Traversal (r a a) (r b b) a b (and potentially other Bitraversable stuff)

8.0.2 is already 3 major stable versions of GHC old, I'd say it's fine to do it.

Generic optics

Perhaps we can adapt lens-sop to generate optics for record datatypes.

Provide `from` or don't.

Perhaps we should expose from in Optics.Iso as a specialisation of re, just for consistency with lens?

#43 (comment)

Options:

-- 1. alias
from = re

-- 2. restricted alias
from = re :: Iso s t a b -> Iso b a t s -- (working with Equality too)

-- 3. no alias

My preference is 3, then 1, then 2.

Package up as a proper library

We should add a .cabal file, switch to depending on existing libraries rather than re-defining standard classes, and so on.

The module interface needs some thought. I'd be inclined to go for something like

  • Optics: main module that re-exports the most useful bits from other modules
  • Optics.{Lens,Traversal,...}: tag definition and functions for working with each different flavour of optic
  • Optics.Internal: the newtype and other key things that live inside the abstraction barrier

More extensive test suite

So far the test suite is fairly minimal. We could do with some more tests that the right things keep working and the wrong things have nice type errors.

Add Review type

There shouldn't be anything difficult about reviews, I just haven't got to them yet.

Hide internals in `optics-core`

They were exposed to make generic-optics Prism derivation.

Would

newtype PrismT a b s t = PT { unPT :: Prism a b a b -> Prism s t a b }

instance P.Profunctor (PrismT s t) where
    dimap f g (PT h) = PT ((iso f g %) . h )

instance P.Choice (PrismT s t) where
    right' (PT h) = PT ((_Right %%) . h)

-- /TODO/ optics-core could/should provide a way to 'coerce'
-- arguments of Optic, the arguments are of representational role; morally.
prismVL'
  :: forall s t a b.
     GL.Prism s t a b
  -> Prism    s t a b
prismVL' f = prism runIdentity Right %% o %% prism Identity Right
  where 
    o :: Prism s (Identity t) a (Identity b)
    o = unPT (f (PT id)) (castOptic equality)

be ok for now. It's not optimal, but good enough. Also soon generic-lens would (in a way or another) expose profunctor optics interface, so we can drop prism runidentity Right % _ % prism Identity Right

Move TH derivation to its own package

I'd much prefer if optics-core would not depend on TH. Just like optics-sop is separate, I think optics-th should be separate as well.

For SOP, we've done a complicated reorganisation recently to split off TH-independent parts into sop-core. I think not having a necessary TH dependency is a big advantage.

Introduce Field1/... and Swapped classes

Note: it would be nice to have

_1 :: Iso i (Identity a) (Identity b) a b
_1 :: Lens i (a, c) (b, c) a b
...

it's a small thing, but I think it should be easy to do with optics (lens would require QuantifiedConstraints).

Improve documentation

  • The Haddock comments for Optic should explain the new indices introduced in #19.
  • We should explain how subtyping works and include the nice diagram from #19 (comment).
  • The Haddocks could do with a general review, e.g. the documentation for the main Optics module is a bit unstructured.
  • We should document the library's divergences from lens (see #19 (comment) and later).
  • We should explain the philosophy behind the library (perhaps in a README, see #5).

Make an Optic interface a little more abstract (hide indexed implementation details)

Problem: Talk.pdf says "This just works, because our Lens type isn’t polymorphic"

But it's not true. The true 'Lens' type in current @Optics@ is

forall i. Lens i s t a b

i.e. we have leave it free. Not good.
(We can set it to () or Void, to put into container,
but then we cannot compose with those optics)

Also the double indices leak the implementation details. Not good either.

Solition: We can cure this, with some additional hackery.
And we can make CheckIndices less hacky, as it would only match on type level list!
I only comment the parts which are changed.

The idea is to use

newtype Optic (k :: OptikKind) (is :: [Type) s t a b
    Optic (forall p i. Constraints k p => p i a b -> p (Curry is i) s t)

abstract interface. I.e. move the first (of double indices) to be inside the
Optic.

Note: The implementation is still a compromise.

  • A_Setter has always empty index list
  • An_IxSettter has always non empty index list
    But we are forced to it this way so Constraints reduce properly. i.e. we can
    sub Setter into IxSetter, but it would be unusable (as it would have empty
    index list.

We can use CheckIndices (and CheckNonIndices if we like) to get better errors.
I think latter is not needed as library doesn't give means to construct
non-indexed optic with non-empty index-list.

More elegantly we'd only have A_Setter and differentiate
between indexed and non-indexed variants based on index-list.
But then the type-checker will fight back.

  • Making Constraints (k :: OpticKind) (is :: [*]) p breaks Is implementation:
    in implies is is rigid, so type-family cannot match on it (or we need more
    proofs)

  • Using Constraints An_Traversal p = IxTraversing p
    but then e.g. over would need to be implemented using IxFunArrow,
    e.g. passing some dummy index value. Not good.

The full gist is in https://gist.github.com/phadej/0a1b1aa95f102a78d4e26b82f45da903

@arybczak What do you think about this?

Set up CI

We may or may not have any tests, but we could at least make sure the thing builds on GHC 7.10 and up.

Another package named e.g. `optics-extra`

I think there needs to be another package between optics-core and optics-th that contains stuff that TH uses, but also pulls in a couple more dependencies for completeness of instances.

E.g. TH needs Data.Data.Optics (#80), but it:

  • has nothing to do with TH
  • requires unordered-containers

TH generation also needs

  • Control.Lens.At - Not yet ported, I think can be worked around.
  • Control.Lens.Wrapped - This is used for generation of Wrapped instances for newtypes. We can skip it for now. The code is mostly trivial, but it has tons of dependencies.
  • Data.Tuple.Optics - We can provide local versions of these if we really want.

Currently Optics.Each and indexed optics are missing instances for unordered-containers (and lesser extent text, bytestring, vector). I think it would be good to fit these into optics-extra (along with Data.Data.Optics) and add missing instances.

Or somewhere.

Minor naming suggestions

I would like to suggest the following renames:

  • Strip the A_ prefix from the optic tags (i.e. A_LensLens)
  • Rename Is to IsA
  • Rename Optic to A

Then a constraint would read like:

Lens `IsA` Getter

.... and an optic type would read like:

A Lens (a, b) a

... which would no longer require a type synonym

I don't feel very strongly about this, so feel free to reject this suggestion if you disagree :)

Add README

This should explain the motivation behind optics, basic usage and the differences from lens.

Use profunctor encoding internally (and get things going forward)

Hey,

After doing Haskell eXchange presentation about profunctor optics and talking things with @adamgundry and @dcoutts I decided to fiddle more with these to get full hierarchy going (with help of @phadej blog posts, purescript-profunctor-lenses and mezzolens libraries) and see how these realistically behave. I succeeded and here it is:

type Optic p a b s t = p a b -> p s t

type Iso a b s t =
  forall p. Profunctor p => Optic p a b s t

type Lens a b s t =
  forall p. Strong p => Optic p a b s t

type Prism a b s t =
  forall p. Choice p => Optic p a b s t

type AffineTraversal a b s t =
  forall p. (Choice p, Strong p) => Optic p a b s t

-- p = Star
type Traversal a b s t =
  forall p. Traversing p => Optic p a b s t

-- p = (->)
type Setter a b s t =
  forall p. Mapping p => Optic p a b s t

-- p = Forget. This is needed because without it 're' would turn 'Prism' into
-- 'Getter', but 'Getter' into 'Review', hence re . re /= id.
type PrismaticGetter a s =
  forall p. Cochoice p => Optic p a a s s

-- p = Forget, view1 for extraction of exactly one value.
type Getter a s =
  forall p. (Bicontravariant p, Cochoice p, Strong p) => Optic p a a s s

-- p = ForgetM, view01 for extraction of at most one value.
type AffineFold a b s t =
  forall p. (Bicontravariant p, Choice p, Cochoice p, Strong p) => Optic p a b s t

-- p = Forget (+ Monoid), viewN for extraction of multiple elements and folding
-- them into one with Monoid instance.
type Fold a b s t =
  forall p. (Bicontravariant p, Cochoice p, Traversing p) => Optic p a b s t

-- p = Tagged. Same concept as PrismaticGetter for re . re = id.
type LensyReview b t
  = forall p. Costrong p => Optic p b b t t

-- p = Tagged, review for reviewing Iso, Prism or LensyReview.
type Review b t =
  forall p. (Bifunctor p, Choice p, Costrong p) => Optic p b b t t

hierarchy

Blue lines mean that optics are convertible between each other with re.

This is somewhat elegant and doesn't require A* equivalents for all optics (as in the lens library) for additional confusion, also folds don't expose internals in the signature, i.e. it's:

foldrOf' :: Fold a b s t -> (a -> r -> r) -> r -> s -> r

not

foldrOf' :: Getting (Dual (Endo (Endo r))) s a -> (a -> r -> r) -> r -> s -> r 

This also adds affine versions of Fold and Traversal, thus solving the view problem, i.e. there are three version of view and it's clear from their signatures what's going on:

-- Helper type. It's Profunctor, Strong, Choice, Cochoice and Bicontravariant
-- (same as Forget).
newtype ForgetM r a b = ForgetM { runForgetM :: a -> Maybe r }

view1 :: Getter a s -> s -> a
view1 optic = runForget (optic (Forget id))

view01 :: AffineFold a b s t -> s -> Maybe a
view01 optic = runForgetM (optic (ForgetM Just))

-- Here Monoid is needed because we use the fact that Forget is Choice.
viewN :: Monoid a => Fold a b s t -> s -> a
viewN optic = runForget (optic (Forget id))

The only problem (apart from exposing dependence on profunctors)? Abysmal error messages.

I watched Adam's presentation from last year's Haskell eXchange and here we go:

λ> set (to fst)

<interactive>:13:6: error:
    • Could not deduce (Bicontravariant p) arising from a use of ‘to’
      from the context: Mapping p
        bound by a type expected by the context:
                   Setter a a (a, b) (a, b)
        at <interactive>:13:1-12
      Possible fix:
        add (Bicontravariant p) to the context of
          a type expected by the context:
            Setter a a (a, b) (a, b)
    • In the first argument of ‘set’, namely ‘(to fst)’
      In the expression: set (to fst)
      In an equation for ‘it’: it = set (to fst)

So at this point I'm fairly convinced that this needs to be gated beyond an abstraction. As there doesn't seem to be much going on with this library, I'd propose to convert it to profunctor encoding (additional benefit is that as it would be internal, we don't need to depend on profunctors library, we can just copy over necessary bits and there aren't that many) since I have written most of the code already, I'd just need to stick all of these signatures into a newtype and rework the subtyping machinery a little bit.

Bonuses:

The implementation of re. We can take care of all the inversions with one function.

newtype Re p a b s t = Re { unRe :: p t s -> p b a }

instance Profunctor p => Profunctor (Re p a b) where
  dimap f g (Re p) = Re (p . dimap g f)

instance Bicontravariant p => Bifunctor (Re p a b) where
  bimap f g (Re p) = Re (p . contrabimap g f)

instance Bifunctor p => Bicontravariant (Re p a b) where
  contrabimap f g (Re p) = Re (p . bimap g f)

instance Strong p => Costrong (Re p a b) where
  unfirst  (Re p) = Re (p . first')
  unsecond (Re p) = Re (p . second')

instance Costrong p => Strong (Re p a b) where
  first'  (Re p) = Re (p . unfirst)
  second' (Re p) = Re (p . unsecond)

instance Choice p => Cochoice (Re p a b) where
  unleft  (Re p) = Re (p . left')
  unright (Re p) = Re (p . right')

instance Cochoice p => Choice (Re p a b) where
  left'  (Re p) = Re (p . unleft)
  right' (Re p) = Re (p . unright)

-- | Inverts optics, turning around 'Iso' into 'Iso', 'Prism' into
-- 'PrismaticGetter' (and back), 'Lens' into 'LensyReview' (and back) and
-- 'Getter' into 'Review' (and back).
re :: Optic (Re p a b) a b s t -> Optic p t s b a
re optic = unRe (optic (Re id))

We can also easily convert from Van Laarhoven lens to a profunctor one:

-- | Taken from mezzolens library. For more information see
-- https://r6research.livejournal.com/28432.html
data PStore a b t = PStore (b -> t) a
  deriving Functor

-- | Convert from a Van Laarhoven lens to a profunctor lens.
vl :: (forall f. Functor f => (a -> f b) -> s -> f t)
   -> Lens a b s t
vl l =
  dimap ((\(PStore f a) -> (f, a)) . l (PStore id))
        (\(f, b) -> f b)
  . second' -- p (b -> t, a) (b -> t, b)

Thoughts and comments are welcome.

And Ixed and At classes

If we depend on containers these would provide tools to work with maps.

Unlike lens, Ixed could provide affine traversal.

Rename *WithIndex to Ix*

This is good for consistency, and as a bonus avoids clashes with lens.

I'll do this when there are no much other stuff in flight.

Utilize monoidal structure of (Ix)Traversal and (Ix)Fold

foldMempty :: Fold s a
foldMempty = foldVL $ \_ _ -> pure ()

foldMappend
  :: (Is k A_Fold, Is l A_Fold)
  => Optic' k is s a
  -> Optic' l is s a
  -> Fold s a
foldMappend o o' = foldVL $ \f s -> traverseOf_ o f s *> traverseOf_ o' f s
λ> toListOf (folded % foldMappend _1 (_2 % _Left)) [("how", Left "are"), ("you", Right ())] 
["how","are","you"]

It seems like providing instances might be problematic due to types, but utility functions will be nice for things like has.

Depending on base vs indexed optics

For indexed optics to be useful we need a bunch of Functor/Foldable/TraversableWithIndex instances for commonly used containers. However, that will incur dependency on containers, unordered-containers and vector (possibly bytestring and text). Is there a satisfactory solution to that problem?

All I can think of is providing these instances (and other stuff that requires mtl or transformers) in package named e.g. optics-extra as orphan instances, but that's probably a bad idea.

Expose CheckIndices for documentation purposes and cleaner signatures

Currently we have this:

λ> :i iover
iover ::
  (optics-core-0.1:Optics.Internal.Indexed.CheckIndices i is,
   Is k A_Setter) =>
  Optic k is s t a b -> (i -> a -> b) -> s -> t
        -- Defined in ‘optics-core-0.1:Optics.Internal.IxSetter’

Also haddock doesn't let you go to definition of CheckIndices. We can expose CheckIndices, but make it have a hidden superclass so that it's not possible to add instances.

Eta-reduce type-synonyms

Now (soon)

type Lens s t a b = Optic A_Lens '[] s t a b

Or should we have just

type Lens = Optic A_Lens '[]

Merge Optics.<flavor> and Optic.<flavor>.Internal

I think there is no good reason for having the split, all pretty much all non-internal flavor specific modules do is just reexport stuff.

If any flavor needs things that are actually internal (and usable by other internal modules), we can have them for these.

Use data kind for optic flavours?

At the moment we use empty types as optic flavour tags (A_Lens etc.), but with the reorganisation in #19 we can't extend the hierarchy later, so we don't need an open kind. (Such extensions were never really well-supported anyway, because of Join.) Would it be worth switching to a promoted data type? That would rule out ill-formed types such as Optic Int i o s t a b.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.