From c08a67033777f0fef08aab8820a5a753a30ed961 Mon Sep 17 00:00:00 2001 From: Csongor Kiss Date: Tue, 11 Feb 2020 16:45:16 +0000 Subject: [PATCH] Update readme and cabal metadata --- README.md | 389 ++-------------------- generic-lens-core/generic-lens-core.cabal | 6 + generic-lens/generic-lens.cabal | 4 +- generic-optics/generic-optics.cabal | 3 + 4 files changed, 43 insertions(+), 359 deletions(-) diff --git a/README.md b/README.md index f510cc7..0fa8698 100644 --- a/README.md +++ b/README.md @@ -3,46 +3,38 @@ [![Build Status](https://travis-ci.org/kcsongor/generic-lens.svg?branch=master)](https://travis-ci.org/kcsongor/generic-lens) [![Hackage](https://img.shields.io/hackage/v/generic-lens.svg)](https://hackage.haskell.org/package/generic-lens) -Generically derive traversals, lenses and prisms. - -Available on [Hackage](https://hackage.haskell.org/package/generic-lens) +Generically derive isos, traversals, lenses and prisms. This library uses `GHC.Generics` to derive efficient optics (traversals, lenses and prisms) for algebraic data types in a type-directed way, with a focus on good type inference and error messages when possible. -The derived optics use the so-called van Laarhoven representation, thus are -fully interoperable with the combinators found in mainstream lens libraries. - -Examples can be found in the `examples` and `tests` folders. - The library is described in the paper: > Csongor Kiss, Matthew Pickering, and Nicolas Wu. 2018. Generic deriving of generic traversals. Proc. ACM Program. Lang. 2, ICFP, Article 85 (July 2018), 30 pages. DOI: https://doi.org/10.1145/3236780 -Table of contents -================= - -* [Preliminaries](#preliminaries) -* [Taxonomy of optics](#taxonomy-of-optics) - * [Lenses](#lenses) - * [By name](#by-name) - * [By position](#by-position) - * [By type](#by-type) - * [By structure](#by-structure) - * [Traversals](#traversals) - * [By type](#by-type-1) - * [By parameter](#by-parameter) - * [By constraint](#by-constraint) - * [Prisms](#prisms) - * [By name](#by-name-1) - * [By type](#by-type-2) -* [Performance](#performance) - * [Inspection testing](#inspection-testing) - * [Benchmarks](#benchmarks) -* [Contributors](#contributors) - -# Preliminaries -A typical module using `generic-lens` will usually have the following +## Package structure + +* [`generic-lens`](https://hackage.haskell.org/package/generic-lens): + Generic derivation of van Laarhoven optics, compatible with the + [`lens`](https://hackage.haskell.org/package/lens) library. There + is no dependency on `lens`, however, and other mainstream lens libraries + sharing the same interface are supported just as well. + +* [`generic-optics`](https://hackage.haskell.org/package/generic-optics): + Generic derivation of optics compatible with the [`optics`](https://hackage.haskell.org/package/optics) + library. + +* [`generic-lens-core`](https://hackage.haskell.org/package/generic-lens-core): + The bulk of the work happens here. Indeed, `generic-lens` and + `generic-optics` are just thin layers on top of `generic-lens-core`. + +`generic-lens` are `generic-optics` are designed to be drop-in +replacements for each other. This means that they share the same module names. If, for +whatever reason, they are both used in the same project, the module imports can be disambiguated +using the `-XPackageImports` extension. + +## Getting started +A typical module using `generic-lens` or `generic-optics` will usually have the following extensions turned on: ```haskell {-# LANGUAGE AllowAmbiguousTypes #-} @@ -52,331 +44,17 @@ extensions turned on: {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE TypeApplications #-} - -``` - -# Taxonomy of optics -Here is a comprehensive list of the optics exposed by `generic-lens`. The -combinators each allow a different way of identifying certain parts of -algebraic data types. - -## Lenses - -A lens identifies exactly one part of a product type, and allows querying and -updating it. - -### By name - -```haskell -data Person = Person { name :: String, age :: Int } deriving (Generic, Show) - -sally :: Person -sally = Person "Sally" 25 -``` - -Record fields can be accessed by their label using the `field` lens. - -```haskell ->>> sally ^. field @"name" -"Sally" - ->>> sally & field @"name" .~ "Tamas" -Person {name = "Tamas", age = 25} -``` -Here we use [visible type application](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#visible-type-application) -to specify which field we're interested in, and use the `^.` and `.~` combinators from a lens library -([lens](https://hackage.haskell.org/package/lens), [microlens](https://hackage.haskell.org/package/microlens), etc.) -to query and update the field. - -Or for standalone use, the `getField` and `setField` functions can be used instead. -```haskell ->>> getField @"age" sally -25 - ->>> setField @"age" 26 sally -Person {name = "Sally", age = 26} -``` - -When a non-existent field is requested, the library generates a helpful type error: -```haskell ->>> sally ^. field @"pet" -error: - • The type Person does not contain a field named 'pet' -``` - -For types with multiple constructors, we can still use `field` as long as all constructors contain the required field -```haskell -data Two - = First { wurble :: String, banana :: Int } - | Second { wurble :: String } - deriving (Generic, Show) - ->>> Second "woops" ^. field @"wurble" -"woops" ->>> Second "woops" ^. field @"banana" - ... - • Not all constructors of the type Two - contain a field named 'banana'. - The offending constructors are: - • Second - ... -``` - -The type of `field` is -```haskell -field :: HasField name s t a b => Lens s t a b -``` -Therefore it allows polymorphic (type-changing) updates, when the accessed field mentions type parameters. - -```haskell -data Foo f a = Foo - { foo :: f a - } deriving (Generic, Show) - -foo1 :: Foo Maybe Int -foo1 = Foo (Just 10) - --- | --- >>> foo2 --- Foo {foo = ["10"]} -foo2 :: Foo [] String -foo2 = foo1 & field @"foo" %~ (maybeToList . fmap show) -``` -This example shows that higher-kinded parameters can also be changed (`Maybe` --> `[]`). We turn a `Foo Maybe Int` into a `Foo [] String` by turning the inner -`Maybe Int` into a `[String]`. - -With `DuplicateRecordFields`, multiple data types can share the same field -name, and the `field` lens works in this case too. No more field name -prefixing! - -### By position - -Fields can be accessed by their position in the data structure (index starting at 1): - -```haskell -data Point = Point Int Int Int deriving (Generic, Show) -data Polygon = Polygon Point Point Point deriving (Generic, Show) - -polygon :: Polygon -polygon = Polygon (Point 1 5 3) (Point 2 4 2) (Point 5 7 (-2)) -``` - -```haskell ->>> polygon ^. position @1 . position @2 -5 - ->>> polygon & position @3 . position @2 %~ (+10) -Polygon (Point 1 5 3) (Point 2 4 2) (Point 5 17 (-2)) - ->>> polygon ^. position @10 -error: - • The type Polygon does not contain a field at position 10 -``` - -Since tuples are an instance of `Generic`, the positional lens readily works: - -```haskell ->>> (("hello", True), 5) ^. position @1 . position @2 -True ->>> (("hello", True, "or"), 5, "even", "longer") ^. position @1 . position @2 -True -``` - -### By type - -Fields can be accessed by their type in the data structure, assuming that this -type is unique: - -```haskell ->>> sally ^. typed @String -"Sally" - ->>> setTyped @Int sally 26 -Person {name = "Sally", age = 26} ``` -### By structure +In particular, `NoMonomorphismRestriction` can be helpful as it +enables proper type inference for top-level functions without a type +signature. -The `super` lens generalises the `field` lens to focus on a collection rather -than a single field. -We can say that a record is a (structural) `subtype' of another, if its fields -are a superset of those of the other. +For usage examples, visit the documentation on hackage, or have a look +in the `examples` and `tests` folders in `generic-lens` or +`generic-optics`. -```haskell -data Human = Human - { name :: String - , age :: Int - , address :: String - } deriving (Generic, Show) - -data Animal = Animal - { name :: String - , age :: Int - } deriving (Generic, Show) - -human :: Human -human = Human {name = "Tunyasz", age = 50, address = "London"} -``` - -```haskell ->>> human ^. super @Animal -Animal {name = "Tunyasz", age = 50} - ->>> upcast human :: Animal -Animal {name = "Tunyasz", age = 50} -``` - -We can apply a function that operates on a supertype to the larger (subtype) -structure, by focusing on the supertype first: - -```haskell -growUp :: Animal -> Animal -growUp (Animal name age) = Animal name (age + 50) - ->>> human & super @Animal %~ growUp -Human {name = "Tunyasz", age = 60, address = "London"} -``` - -## Traversals - -Traversals allow multiple values to be queried or updated at the same time. - -As a running example, consider a data type of weighted trees. There are two -type parameters, which correspond to the type of elements and weights in the -tree: -```haskell -data WTree a w - = Leaf a - | Fork (WTree a w) (WTree a w) - | WithWeight (WTree a w) w - deriving (Generic, Show) - -mytree :: WTree Int Int -mytree = Fork (WithWeight (Leaf 42) 1) - (WithWeight (Fork (Leaf 88) (Leaf 37)) 2) -``` - -### By type -Focus on all values of a given type. - -```haskell -types :: HasTypes s a => Traversal' s a -``` - -```haskell ->>> toListOf (types @Int) myTree -[42,1,88,37,2] -``` - -Note that this traversal is "deep": the subtrees are recursively traversed. - -### By parameter -As the above example shows, the `types` traversal is limited in that it cannot -distinguish between the two types of `Int`s: the weights and the values. - -Instead, the `param` traversal allows specifying types that correspond to a -certain type parameter. -```haskell -param :: HasParam pos s t a b => Traversal s t a b -``` - -```haskell ->>> toListOf (param @1) myTree -[42,88,37] -``` - -Here, the numbering starts from 0, with 0 being the rightmost parameter. -Because `param` is guaranteed to focus on parametric values, it allows the type -to be changed as well. - -For example, we can implement `Functor` for `WTree` as simply as: - -```haskell -instance Functor (WTree a) where - fmap = over (param @0) -``` - -## Prisms - -A prism focuses on one part of a sum type (which might not be present). Other -than querying the type, they can be "turned around" to inject the data into the -sum (like a constructor). - -### By name - -Constructor components can be accessed using the constructor's name: - -```haskell -type Name = String -type Age = Int - -data Dog = MkDog { name :: Name, age :: Age } deriving (Generic, Show) -data Animal = Dog Dog | Cat Name Age | Duck Age deriving (Generic, Show) - -shep = Dog (MkDog "Shep" 4) -mog = Cat "Mog" 5 -donald = Duck 4 -``` - -```haskell ->>> shep ^? _Ctor @"Dog" -Just (MkDog {name = "Shep", age = 4}) - ->>> shep ^? _Ctor @"Cat" -Nothing -``` - -Constructors with multiple fields can be focused as a tuple - -``` ->>> mog ^? _Ctor @"Cat" -Just ("Mog",5) - ->>> _Ctor @"Cat" # ("Garfield", 6) :: Animal -Cat "Garfield" 6 - -``` - -### By type - -Constructor components can be accessed using the component's type, assuming -that this type is unique: - -```haskell -type Name = String -type Age = Int - -data Dog = MkDog { name :: Name, age :: Age } deriving (Generic, Show) -data Animal = Dog Dog | Cat Name Age | Duck Age deriving (Generic, Show) - -shep = Dog (MkDog "Shep" 4) -mog = Cat "Mog" 5 -donald = Duck 4 -``` - -```haskell ->>> mog ^? _Typed @Dog -Nothing - ->>> shep ^? _Typed @Dog -Just (MkDog {name = "Shep", age = 4}) - ->>> donald ^? _Typed @Age -Just 4 - ->>> mog ^? _Typed @(Name, Age) -("Mog", 5) - ->>> donald ^? _Typed @Float -error: - • The type Animal does not contain a constructor whose field is of type Float - ->>> _Typed @Age # 6 :: Animal -Duck 6 -``` - -# Performance +## Performance The runtime characteristics of the derived optics is in most cases identical at `-O1`, in some cases only slightly slower than the hand-written version. This is thanks to GHC's optimiser eliminating the generic overhead. @@ -387,8 +65,3 @@ library is used to ensure (see [here](test/Spec.hs)) that everything gets inlined away. There is also a [benchmark suite](https://github.com/mpickering/generic-lens-benchmarks) with larger, real-world examples. -# Contributors - -+ [Matthew Pickering](https://github.com/mpickering) -+ [Toby Shaw](https://github.com/TobyShaw) -+ [Will Jones](https://github.com/lunaris) diff --git a/generic-lens-core/generic-lens-core.cabal b/generic-lens-core/generic-lens-core.cabal index d3d2120..fcabebc 100644 --- a/generic-lens-core/generic-lens-core.cabal +++ b/generic-lens-core/generic-lens-core.cabal @@ -2,6 +2,12 @@ name: generic-lens-core version: 2.0.0.0 synopsis: Generically derive traversals, lenses and prisms. description: This library uses GHC.Generics to derive efficient optics (traversals, lenses and prisms) for algebraic data types in a type-directed way, with a focus on good type inference and error messages when possible. + . + This package is the shared internal logic of the + @@ + and + @@ + libraries. homepage: https://github.com/kcsongor/generic-lens license: BSD3 diff --git a/generic-lens/generic-lens.cabal b/generic-lens/generic-lens.cabal index 96ba1d5..7736966 100644 --- a/generic-lens/generic-lens.cabal +++ b/generic-lens/generic-lens.cabal @@ -2,6 +2,9 @@ name: generic-lens version: 2.0.0.0 synopsis: Generically derive traversals, lenses and prisms. description: This library uses GHC.Generics to derive efficient optics (traversals, lenses and prisms) for algebraic data types in a type-directed way, with a focus on good type inference and error messages when possible. + . + The library exposes a van Laarhoven interface. For an alternative interface, supporting an opaque optic type, see + @@. homepage: https://github.com/kcsongor/generic-lens license: BSD3 @@ -43,7 +46,6 @@ library build-depends: base >= 4.11 && < 5 , generic-lens-core == 2.0.0.0 - , indexed-profunctors >= 0.1 && < 1.0 , profunctors , text >= 1.2 && < 1.3 diff --git a/generic-optics/generic-optics.cabal b/generic-optics/generic-optics.cabal index e2da168..955ca53 100644 --- a/generic-optics/generic-optics.cabal +++ b/generic-optics/generic-optics.cabal @@ -2,6 +2,9 @@ name: generic-optics version: 2.0.0.0 synopsis: Generically derive traversals, lenses and prisms. description: This library uses GHC.Generics to derive efficient optics (traversals, lenses and prisms) for algebraic data types in a type-directed way, with a focus on good type inference and error messages when possible. + . + The library exposes an @@ interface. For a van Laarhoven interface, see + @@. homepage: https://github.com/kcsongor/generic-lens license: BSD3