prolens (empty) → 0.0.0.0
raw patch · 11 files changed
+1985/−0 lines, 11 filesdep +basedep +doctestdep +hedgehog
Dependencies added: base, doctest, hedgehog, hspec, hspec-hedgehog, inspection-testing, prolens
Files
- CHANGELOG.md +11/−0
- LICENSE +373/−0
- README.md +154/−0
- prolens.cabal +99/−0
- src/Prolens.hs +989/−0
- test/Doctest.hs +15/−0
- test/Spec.hs +15/−0
- test/Test/Data.hs +131/−0
- test/Test/Prolens.hs +43/−0
- test/Test/Prolens/Inspection.hs +87/−0
- test/Test/Prolens/Property.hs +68/−0
+ CHANGELOG.md view
@@ -0,0 +1,11 @@+# Changelog++`prolens` uses [PVP Versioning][1].+The changelog is available [on GitHub][2].++## 0.0.0.0++* Initially created.++[1]: https://pvp.haskell.org+[2]: https://github.com/kowainik/prolens/releases
+ LICENSE view
@@ -0,0 +1,373 @@+Mozilla Public License Version 2.0+==================================++1. Definitions+--------------++1.1. "Contributor"+ means each individual or legal entity that creates, contributes to+ the creation of, or owns Covered Software.++1.2. "Contributor Version"+ means the combination of the Contributions of others (if any) used+ by a Contributor and that particular Contributor's Contribution.++1.3. "Contribution"+ means Covered Software of a particular Contributor.++1.4. "Covered Software"+ means Source Code Form to which the initial Contributor has attached+ the notice in Exhibit A, the Executable Form of such Source Code+ Form, and Modifications of such Source Code Form, in each case+ including portions thereof.++1.5. "Incompatible With Secondary Licenses"+ means++ (a) that the initial Contributor has attached the notice described+ in Exhibit B to the Covered Software; or++ (b) that the Covered Software was made available under the terms of+ version 1.1 or earlier of the License, but not also under the+ terms of a Secondary License.++1.6. "Executable Form"+ means any form of the work other than Source Code Form.++1.7. "Larger Work"+ means a work that combines Covered Software with other material, in+ a separate file or files, that is not Covered Software.++1.8. "License"+ means this document.++1.9. "Licensable"+ means having the right to grant, to the maximum extent possible,+ whether at the time of the initial grant or subsequently, any and+ all of the rights conveyed by this License.++1.10. "Modifications"+ means any of the following:++ (a) any file in Source Code Form that results from an addition to,+ deletion from, or modification of the contents of Covered+ Software; or++ (b) any new file in Source Code Form that contains any Covered+ Software.++1.11. "Patent Claims" of a Contributor+ means any patent claim(s), including without limitation, method,+ process, and apparatus claims, in any patent Licensable by such+ Contributor that would be infringed, but for the grant of the+ License, by the making, using, selling, offering for sale, having+ made, import, or transfer of either its Contributions or its+ Contributor Version.++1.12. "Secondary License"+ means either the GNU General Public License, Version 2.0, the GNU+ Lesser General Public License, Version 2.1, the GNU Affero General+ Public License, Version 3.0, or any later versions of those+ licenses.++1.13. "Source Code Form"+ means the form of the work preferred for making modifications.++1.14. "You" (or "Your")+ means an individual or a legal entity exercising rights under this+ License. For legal entities, "You" includes any entity that+ controls, is controlled by, or is under common control with You. For+ purposes of this definition, "control" means (a) the power, direct+ or indirect, to cause the direction or management of such entity,+ whether by contract or otherwise, or (b) ownership of more than+ fifty percent (50%) of the outstanding shares or beneficial+ ownership of such entity.++2. License Grants and Conditions+--------------------------------++2.1. Grants++Each Contributor hereby grants You a world-wide, royalty-free,+non-exclusive license:++(a) under intellectual property rights (other than patent or trademark)+ Licensable by such Contributor to use, reproduce, make available,+ modify, display, perform, distribute, and otherwise exploit its+ Contributions, either on an unmodified basis, with Modifications, or+ as part of a Larger Work; and++(b) under Patent Claims of such Contributor to make, use, sell, offer+ for sale, have made, import, and otherwise transfer either its+ Contributions or its Contributor Version.++2.2. Effective Date++The licenses granted in Section 2.1 with respect to any Contribution+become effective for each Contribution on the date the Contributor first+distributes such Contribution.++2.3. Limitations on Grant Scope++The licenses granted in this Section 2 are the only rights granted under+this License. No additional rights or licenses will be implied from the+distribution or licensing of Covered Software under this License.+Notwithstanding Section 2.1(b) above, no patent license is granted by a+Contributor:++(a) for any code that a Contributor has removed from Covered Software;+ or++(b) for infringements caused by: (i) Your and any other third party's+ modifications of Covered Software, or (ii) the combination of its+ Contributions with other software (except as part of its Contributor+ Version); or++(c) under Patent Claims infringed by Covered Software in the absence of+ its Contributions.++This License does not grant any rights in the trademarks, service marks,+or logos of any Contributor (except as may be necessary to comply with+the notice requirements in Section 3.4).++2.4. Subsequent Licenses++No Contributor makes additional grants as a result of Your choice to+distribute the Covered Software under a subsequent version of this+License (see Section 10.2) or under the terms of a Secondary License (if+permitted under the terms of Section 3.3).++2.5. Representation++Each Contributor represents that the Contributor believes its+Contributions are its original creation(s) or it has sufficient rights+to grant the rights to its Contributions conveyed by this License.++2.6. Fair Use++This License is not intended to limit any rights You have under+applicable copyright doctrines of fair use, fair dealing, or other+equivalents.++2.7. Conditions++Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted+in Section 2.1.++3. Responsibilities+-------------------++3.1. Distribution of Source Form++All distribution of Covered Software in Source Code Form, including any+Modifications that You create or to which You contribute, must be under+the terms of this License. You must inform recipients that the Source+Code Form of the Covered Software is governed by the terms of this+License, and how they can obtain a copy of this License. You may not+attempt to alter or restrict the recipients' rights in the Source Code+Form.++3.2. Distribution of Executable Form++If You distribute Covered Software in Executable Form then:++(a) such Covered Software must also be made available in Source Code+ Form, as described in Section 3.1, and You must inform recipients of+ the Executable Form how they can obtain a copy of such Source Code+ Form by reasonable means in a timely manner, at a charge no more+ than the cost of distribution to the recipient; and++(b) You may distribute such Executable Form under the terms of this+ License, or sublicense it under different terms, provided that the+ license for the Executable Form does not attempt to limit or alter+ the recipients' rights in the Source Code Form under this License.++3.3. Distribution of a Larger Work++You may create and distribute a Larger Work under terms of Your choice,+provided that You also comply with the requirements of this License for+the Covered Software. If the Larger Work is a combination of Covered+Software with a work governed by one or more Secondary Licenses, and the+Covered Software is not Incompatible With Secondary Licenses, this+License permits You to additionally distribute such Covered Software+under the terms of such Secondary License(s), so that the recipient of+the Larger Work may, at their option, further distribute the Covered+Software under the terms of either this License or such Secondary+License(s).++3.4. Notices++You may not remove or alter the substance of any license notices+(including copyright notices, patent notices, disclaimers of warranty,+or limitations of liability) contained within the Source Code Form of+the Covered Software, except that You may alter any license notices to+the extent required to remedy known factual inaccuracies.++3.5. Application of Additional Terms++You may choose to offer, and to charge a fee for, warranty, support,+indemnity or liability obligations to one or more recipients of Covered+Software. However, You may do so only on Your own behalf, and not on+behalf of any Contributor. You must make it absolutely clear that any+such warranty, support, indemnity, or liability obligation is offered by+You alone, and You hereby agree to indemnify every Contributor for any+liability incurred by such Contributor as a result of warranty, support,+indemnity or liability terms You offer. You may include additional+disclaimers of warranty and limitations of liability specific to any+jurisdiction.++4. Inability to Comply Due to Statute or Regulation+---------------------------------------------------++If it is impossible for You to comply with any of the terms of this+License with respect to some or all of the Covered Software due to+statute, judicial order, or regulation then You must: (a) comply with+the terms of this License to the maximum extent possible; and (b)+describe the limitations and the code they affect. Such description must+be placed in a text file included with all distributions of the Covered+Software under this License. Except to the extent prohibited by statute+or regulation, such description must be sufficiently detailed for a+recipient of ordinary skill to be able to understand it.++5. Termination+--------------++5.1. The rights granted under this License will terminate automatically+if You fail to comply with any of its terms. However, if You become+compliant, then the rights granted under this License from a particular+Contributor are reinstated (a) provisionally, unless and until such+Contributor explicitly and finally terminates Your grants, and (b) on an+ongoing basis, if such Contributor fails to notify You of the+non-compliance by some reasonable means prior to 60 days after You have+come back into compliance. Moreover, Your grants from a particular+Contributor are reinstated on an ongoing basis if such Contributor+notifies You of the non-compliance by some reasonable means, this is the+first time You have received notice of non-compliance with this License+from such Contributor, and You become compliant prior to 30 days after+Your receipt of the notice.++5.2. If You initiate litigation against any entity by asserting a patent+infringement claim (excluding declaratory judgment actions,+counter-claims, and cross-claims) alleging that a Contributor Version+directly or indirectly infringes any patent, then the rights granted to+You by any and all Contributors for the Covered Software under Section+2.1 of this License shall terminate.++5.3. In the event of termination under Sections 5.1 or 5.2 above, all+end user license agreements (excluding distributors and resellers) which+have been validly granted by You or Your distributors under this License+prior to termination shall survive termination.++************************************************************************+* *+* 6. Disclaimer of Warranty *+* ------------------------- *+* *+* Covered Software is provided under this License on an "as is" *+* basis, without warranty of any kind, either expressed, implied, or *+* statutory, including, without limitation, warranties that the *+* Covered Software is free of defects, merchantable, fit for a *+* particular purpose or non-infringing. The entire risk as to the *+* quality and performance of the Covered Software is with You. *+* Should any Covered Software prove defective in any respect, You *+* (not any Contributor) assume the cost of any necessary servicing, *+* repair, or correction. This disclaimer of warranty constitutes an *+* essential part of this License. No use of any Covered Software is *+* authorized under this License except under this disclaimer. *+* *+************************************************************************++************************************************************************+* *+* 7. Limitation of Liability *+* -------------------------- *+* *+* Under no circumstances and under no legal theory, whether tort *+* (including negligence), contract, or otherwise, shall any *+* Contributor, or anyone who distributes Covered Software as *+* permitted above, be liable to You for any direct, indirect, *+* special, incidental, or consequential damages of any character *+* including, without limitation, damages for lost profits, loss of *+* goodwill, work stoppage, computer failure or malfunction, or any *+* and all other commercial damages or losses, even if such party *+* shall have been informed of the possibility of such damages. This *+* limitation of liability shall not apply to liability for death or *+* personal injury resulting from such party's negligence to the *+* extent applicable law prohibits such limitation. Some *+* jurisdictions do not allow the exclusion or limitation of *+* incidental or consequential damages, so this exclusion and *+* limitation may not apply to You. *+* *+************************************************************************++8. Litigation+-------------++Any litigation relating to this License may be brought only in the+courts of a jurisdiction where the defendant maintains its principal+place of business and such litigation shall be governed by laws of that+jurisdiction, without reference to its conflict-of-law provisions.+Nothing in this Section shall prevent a party's ability to bring+cross-claims or counter-claims.++9. Miscellaneous+----------------++This License represents the complete agreement concerning the subject+matter hereof. If any provision of this License is held to be+unenforceable, such provision shall be reformed only to the extent+necessary to make it enforceable. Any law or regulation which provides+that the language of a contract shall be construed against the drafter+shall not be used to construe this License against a Contributor.++10. Versions of the License+---------------------------++10.1. New Versions++Mozilla Foundation is the license steward. Except as provided in Section+10.3, no one other than the license steward has the right to modify or+publish new versions of this License. Each version will be given a+distinguishing version number.++10.2. Effect of New Versions++You may distribute the Covered Software under the terms of the version+of the License under which You originally received the Covered Software,+or under the terms of any subsequent version published by the license+steward.++10.3. Modified Versions++If you create software not governed by this License, and you want to+create a new license for such software, you may create and use a+modified version of this License if you rename the license and remove+any references to the name of the license steward (except to note that+such modified license differs from this License).++10.4. Distributing Source Code Form that is Incompatible With Secondary+Licenses++If You choose to distribute Source Code Form that is Incompatible With+Secondary Licenses under the terms of this version of the License, the+notice described in Exhibit B of this License must be attached.++Exhibit A - Source Code Form License Notice+-------------------------------------------++ This Source Code Form is subject to the terms of the Mozilla Public+ License, v. 2.0. If a copy of the MPL was not distributed with this+ file, You can obtain one at http://mozilla.org/MPL/2.0/.++If it is not possible or desirable to put the notice in a particular+file, then You may include the notice in a location (such as a LICENSE+file in a relevant directory) where a recipient would be likely to look+for such a notice.++You may add additional accurate notices of copyright ownership.++Exhibit B - "Incompatible With Secondary Licenses" Notice+---------------------------------------------------------++ This Source Code Form is "Incompatible With Secondary Licenses", as+ defined by the Mozilla Public License, v. 2.0.
+ README.md view
@@ -0,0 +1,154 @@+# prolens++++[](https://github.com/kowainik/prolens/actions)+[](https://hackage.haskell.org/package/prolens)+[](LICENSE)++The `prolens` package is a Haskell library with a __minimal__ and+__lightweight__ implementation of _optics_. __Optic__ is a high-level+concept for values that provide _composable_ access to different parts of+structures.++Prolens implements the following optics:++* __Lens__ — composable getters and setters+* __Prism__ — composable constructors and deconstructors+* __Traversal__ — composable data structures visitors++## Goals++We created the `prolens` project in pursuit of the following goals:++1. __Education__. Teach others how to implement and work with+ profunctor optics. This also means that some underlying types or+ type variables have different unconventional names+2. __Learning__. Explore new concepts ourselves and understand better+ abstractions used in the implementation.+3. __Minimalism__. Keep the number of dependencies, features and code+ low, but still solve common problems.+4. __Performance__. Despite being minimalist, implement optics so they+ are as fast as manual and clumsy pattern matching.+5. __Exploration__. Understand how different modern Haskell features+ can work on improving interface and bring new flavour into standard+ approaches. Because of this, we implement our own `Profunctor`+ typeclass with the+ [QuantifiedConstraints](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/glasgow_exts.html#quantified-constraints)+ feature, which is not present in any other library at the moment.+6. __Profunctors__. We use profunctor encoding of optics because it+ has more elegant design with fewer surprises.++## Features++1. __Lightweight__. Only+ [base](http://hackage.haskell.org/package/base)+ in dependencies. The project itself also has a rather small amount+ of code.+2. __Fast__. Despite being lightweight, `prolens` provides a+ performant API. We use the+ [inspection-testing](https://hackage.haskell.org/package/inspection-testing)+ library to guarantee that our implementation of optics compiles to+ the same code as plain Haskell getters, record-update syntax and+ pattern matching.+3. __Excellent documentation__. The `prolens` library contains a+ mini-tutorial on optics, enough to understand how and when to use+ basic lenses and prisms.+4. __Beginner-friendly__. The abstractions in the implementation are+ hardcore, but our documentation presents the concept in a+ beginner-friendly and approachable manner.+5. __Lawful__. We use property-based testing to make sure that laws of+ all underlying abstractions are verified.++## How to use++`prolens` is compatible with the latest GHC compiler+versions starting from `8.6.5`.++In order to start using `prolens` in your project, you+will need to set it up with the three easy steps:++1. Add the dependency on `prolens` in your project's+ `.cabal` file. For this, you should modify the `build-depends`+ section by adding the name of this library. After the adjustment,+ this section could look like this:++ ```haskell+ build-depends: base ^>= 4.14+ , prolens ^>= 0.0+ ```+2. In the module where you wish to use composable getters and setters,+ you should add the import:++ ```haskell+ import Prolens (Lens', lens, view)+ ```+3. Now you can use the types and functions from the library:++ ```haskell+ data User = User+ { userName :: String+ , userAge :: Int+ }++ nameL :: Lens' User String+ nameL = lens userName (\u new -> u { userName = new })++ main :: IO ()+ main = putStrln $ view nameL (User "Johnny" 27)+ ```++### Usage with Stack++If `prolens` is not available on your current Stackage+resolver yet, fear not! You can still use it from Hackage by adding+the following to the `extra-deps` section of your `stack.yaml` file:++```yaml+extra-deps:+ - prolens-0.0.0.0+```++## Comparison to other libraries++1. [lens](https://hackage.haskell.org/package/lens)++ It is the most mature Haskell library for optics. `lens` provides a+ richer interface, but it is heavyweight and based on Van Laarhoven (VL)+ encoding of lenses.++2. [microlens](https://hackage.haskell.org/package/microlens)++ A lightweight implementation of optics compatible with+ `lens`. `microlens` is also minimalistic, but it doesn't provide+ prisms and is based on VL encoding.++3. [optics](https://hackage.haskell.org/package/optics)++ The `optics` library uses the profunctor encoding. It provides much+ more features than `prolens`, but at the same time it's+ heavyweight. Also, `optics` uses an opaque representation of optics+ (e.g. they are wrapped in a newtype), which means that they are+ composed using the custom operator `%`, while in `prolens` optics+ are type aliases to functions and can be easily composed with the+ dot `.` operator.++4. [profunctor-optics](https://hackage.haskell.org/package/profunctor-optics)++ This library is also based on profunctor encoding (as the name+ suggests) and provides optics as aliases to functions. But it is+ more heavyweight, though it provides more features.++In addition to this per-library comparison, `prolens` has a few unique+features:++ * Beginner-friendly documentation with usage examples+ * Usage of `inspection-testing` to guarantee the performance of+ optics+ * Property-based tests of lens and typeclasses laws to make sure+ that all abstractions behave properly++## Acknowledgement++ * Edward Kmett for lenses and profunctor typeclasses+ * Well-Typed for the implementation of `optics`
+ prolens.cabal view
@@ -0,0 +1,99 @@+cabal-version: 2.4+name: prolens+version: 0.0.0.0+synopsis: Profunctor-based lightweight implementation of optics+description:+ Lightweight and performance implementation of optics — lenses, prisms, traversals.+ .+ The library uses hardcore abstractions internally, but provides+ beginner-friendly, composable and convenient interface for working+ with data structures.+homepage: https://github.com/kowainik/prolens+bug-reports: https://github.com/kowainik/prolens/issues+license: MPL-2.0+license-file: LICENSE+author: Veronika Romashkina, Dmitrii Kovanikov+maintainer: Kowainik <xrom.xkov@gmail.com>+copyright: 2020 Kowainik+category: Data, Optics, Lenses+build-type: Simple+extra-doc-files: README.md+ CHANGELOG.md+tested-with: GHC == 8.6.5+ GHC == 8.8.4+ GHC == 8.10.2++source-repository head+ type: git+ location: https://github.com/kowainik/prolens.git++common common-options+ build-depends: base >= 4.12.0.0 && < 4.15++ ghc-options: -Wall+ -Wcompat+ -Widentities+ -Wincomplete-uni-patterns+ -Wincomplete-record-updates+ -Wredundant-constraints+ if impl(ghc >= 8.2)+ ghc-options: -fhide-source-paths+ if impl(ghc >= 8.4)+ ghc-options: -Wmissing-export-lists+ -Wpartial-fields+ if impl(ghc >= 8.8)+ ghc-options: -Wmissing-deriving-strategies+ -fwrite-ide-info+ -hiedir=.hie+ if impl(ghc >= 8.10)+ ghc-options: -Wunused-packages++ default-language: Haskell2010+ default-extensions: ConstraintKinds+ DeriveGeneric+ DerivingStrategies+ GeneralizedNewtypeDeriving+ InstanceSigs+ KindSignatures+ LambdaCase+ OverloadedStrings+ RecordWildCards+ ScopedTypeVariables+ StandaloneDeriving+ TupleSections+ TypeApplications+ ViewPatterns++common common-test+ import: common-options+ hs-source-dirs: test+ ghc-options: -threaded++library+ import: common-options+ hs-source-dirs: src+ exposed-modules: Prolens++test-suite prolens-test+ import: common-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs++ other-modules: Test.Data+ Test.Prolens+ Test.Prolens.Property+ Test.Prolens.Inspection++ build-depends: prolens+ , hedgehog >= 1.0.2 && < 2+ , hspec ^>= 2.7.4+ , hspec-hedgehog+ , inspection-testing ^>= 0.4+ ghc-options: -rtsopts+ -with-rtsopts=-N++test-suite doctest+ import: common-test+ type: exitcode-stdio-1.0+ main-is: Doctest.hs+ build-depends: doctest ^>= 0.17
+ src/Prolens.hs view
@@ -0,0 +1,989 @@+{-# OPTIONS_GHC -Wno-redundant-constraints #-}++{-# LANGUAGE QuantifiedConstraints #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Copyright: (c) 2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++The @prolens@ package is a Haskell library with a minimal and lightweight+implementation of optics based on 'Profunctor's. __'Optic'__ is a high-level+concept for values that provide composable access to different parts of structures.++"Prolens" implements the following optics:++ * 'Lens' — composable getters and setters+ * 'Prism' — composable constructors and deconstructors+ * 'Traversal' — composable data structures visitors++== Usage++To use lenses or prisms in your project, you need to add @prolens@ package as+the dependency in the @build-depends@ field of your @.cabal@ file. E.g.:++@+build-depends: prolens ^>= 0.0.0.0+@++You should add the import of this module in the place of lenses usage:++@+__import__ "Prolens"+@++== Creating your own optics++We show in each section of this module how to create values of each+kind of optics.++⚠️ __The general crucial rule__ for achieving maximum performance:+always add @\{\-\# INLINE ... \#\-\}@ pragmas to your optics.++== Typeclasses table++The below table shows required constraints for each 'Optic':+++-------------+------------------------------++| Optic | Constraints |++=============+==============================++| 'Lens' | @'Strong' p@ |++-------------+------------------------------++| 'Prism' | @'Choice' p@ |++-------------+------------------------------++| 'Traversal' | @('Choice' p, 'Monoidal' p)@ |++-------------+------------------------------+++== Usage table: get, set, modify++Here is a go-to table on how to use getter, setters and modifiers with different+'Optic's.+++-------------+------------------+--------------+------------------+------------------+-----------------+-----------------++| | get | get operator | set | set operator | modify | modify operator |++=============+==================+==============+==================+==================+=================+=================++| 'Lens' | @'view' l x@ | @x '^.' l@ | @'set' l new x@ | @x & l '.~' new@ | @'over' l f x@ | @x & l '%~' f@ |++-------------+------------------+--------------+------------------+------------------+-----------------+-----------------++| 'Prism' | @'preview' _L x@ | - | @'set' _L new x@ | - | @'over' _L f x@ | - |++-------------+------------------+--------------+------------------+------------------+-----------------+-----------------++| 'Traversal' | @'view' l x@ | - | @'set' l new x@ | - | @'over' l f x@ | - |++-------------+------------------+--------------+------------------+------------------+-----------------+-----------------+++@since 0.0.0.0+-}++module Prolens+ ( -- * Profunctor typeclass+ Profunctor (..)++ -- * Optics+ , Optic++ -- * Lenses+ -- $lenses++ -- ** Lenses types+ , Lens+ , Lens'+ -- ** Strong typeclass+ , Strong (..)++ -- ** Lenses functions+ , set+ , over+ , view+ , lens++ -- ** Lenses operators+ , (^.)+ , (.~)+ , (%~)++ -- ** Standard lenses+ , fstL+ , sndL++ -- * Prisms+ -- $prisms++ -- ** Prism types+ , Prism+ , Prism'+ -- ** Choice typeclass+ , Choice (..)++ -- ** Prism functions+ , prism+ , prism'+ , preview++ -- ** Standard Prisms+ , _Just+ , _Left+ , _Right++ -- * Traversals++ -- ** Traversal types+ , Traversal+ -- ** Monoidal typeclass+ , Monoidal (..)++ -- ** Traversal functions+ , traverseOf++ -- ** Standard traversals+ , eachPair+ , eachMaybe+ , eachList++ -- * Internal data types+ , Forget (..)+ , Fun (..)+ ) where++import Control.Applicative (Const (..), liftA2)+import Data.Coerce (coerce)+import Data.Monoid (First (..))++-- $setup+-- >>> import Data.Function ((&))+++{- | The type @p@ is called 'Profunctor' and it means, that a value of+type @p in out@ takes a value of type @in@ as an argument (input) and+outputs a value of type @out@. 'Profunctor' allows mapping of inputs+and outputs.++@+ +----> Result input+ |+ | +--> Original profunctor+ | +-> Original input |+ + + ++dimap :: (in2 -> in1) -> (out1 -> out2) -> p in1 out1 -> p in2 out2+ + ++ | +-> Result output+ |+ +-> Original output+@++Speaking in terms of other abstractions, 'Profunctor' is+'Data.Functor.Contravariant.Contravariant' in the first type argument+(type variable @in@) and 'Functor' in the second type argument (type+variable @out@).++Moreover, @p in@ must have 'Functor' instance first to implement the+'Profunctor' instance. This required using @QuantifiedConstraints@.++@+ Contravariant <---++ |+ +-+-++ + ++(forall a . Functor (p a)) => Profunctor p a b+ + + ++ | | |+ +--> Quantified constraint ++++ |+ Functor <--++@++Instances of 'Profunctor' should satisfy the following laws:++* __Identity:__ @'dimap' 'id' 'id' ≡ 'id'@+* __Composition:__ @'dimap' (inAB . inBC) (outYZ . outXY) ≡ 'dimap' outBC outYZ . 'dimap' outAB outXY@++@since 0.0.0.0+-}+-- type Profunctor :: (Type -> Type -> Type) -> Constraint+class (forall a . Functor (p a)) => Profunctor p where+ dimap+ :: (in2 -> in1) -- ^ Map input+ -> (out1 -> out2) -- ^ Map output+ -> p in1 out1 -- ^ Take @in1@ as input and return @out1@+ -> p in2 out2 -- ^ Take @in2@ as input and return @out2@++-- | @since 0.0.0.0+instance Profunctor (->) where+ dimap :: (in2 -> in1) -> (out1 -> out2) -> (in1 -> out1) -> (in2 -> out2)+ dimap in21 out12 f = out12 . f . in21+ {-# INLINE dimap #-}++{- | @'Fun' m a b@ is a wrapper for function @a -> m b@.++@since 0.0.0.0+-}+newtype Fun m a b = Fun+ { unFun :: a -> m b+ }++-- | @since 0.0.0.0+instance Functor m => Functor (Fun m x) where+ fmap :: (a -> b) -> Fun m x a -> Fun m x b+ fmap f (Fun xma) = Fun (fmap f . xma)+ {-# INLINE fmap #-}++-- | @since 0.0.0.0+instance Functor m => Profunctor (Fun m) where+ dimap :: (in2 -> in1) -> (out1 -> out2) -> Fun m in1 out1 -> Fun m in2 out2+ dimap in21 out12 (Fun f) = Fun (fmap out12 . f . in21)+ {-# INLINE dimap #-}++{- | 'Strong' is a 'Profunctor' that can be lifted to take a pair as+an input and return a pair.++The second element of a pair (variable of type @c@) can be of any+type, and you can decide what type it should be. This is convenient+for implementing various functions. E.g. 'lens' uses this fact.++@since 0.0.0.0+-}+class Profunctor p => Strong p where+ first :: p a b -> p (a, c) (b, c)+ second :: p a b -> p (c, a) (c, b)++-- | @since 0.0.0.0+instance Strong (->) where+ first :: (a -> b) -> (a, c) -> (b, c)+ first ab (a, c) = (ab a, c)+ {-# INLINE first #-}++ second :: (a -> b) -> (c, a) -> (c, b)+ second ab (c, a) = (c, ab a)+ {-# INLINE second #-}++-- | @since 0.0.0.0+instance (Functor m) => Strong (Fun m) where+ first :: Fun m a b -> Fun m (a, c) (b, c)+ first (Fun amb) = Fun (\(a, c) -> fmap (, c) (amb a))+ {-# INLINE first #-}++ second :: Fun m a b -> Fun m (c, a) (c, b)+ second (Fun amb) = Fun (\(c, a) -> fmap (c,) (amb a))+ {-# INLINE second #-}++{- | 'Choice' is a 'Profunctor' that can be lifted to work with+'Either' given input or some other value.++The other element of 'Either' (variable of type @c@) can be of any+type, and you can decide what type it should be. This is convenient+for implementing various functions. E.g. 'prism' uses this fact.++@since 0.0.0.0+-}+class Profunctor p => Choice p where+ left :: p a b -> p (Either a c) (Either b c)+ right :: p a b -> p (Either c a) (Either c b)++-- | @since 0.0.0.0+instance Choice (->) where+ left :: (a -> b) -> Either a c -> Either b c+ left ab = \case+ Left a -> Left $ ab a+ Right c -> Right c+ {-# INLINE left #-}++ right :: (a -> b) -> Either c a -> Either c b+ right ab = \case+ Right a -> Right $ ab a+ Left c -> Left c+ {-# INLINE right #-}++-- | @since 0.0.0.0+instance (Applicative m) => Choice (Fun m) where+ left :: Fun m a b -> Fun m (Either a c) (Either b c)+ left (Fun amb)= Fun $ \eitherAc -> case eitherAc of+ Left a -> Left <$> amb a+ Right c -> pure $ Right c+ {-# INLINE left #-}++ right :: Fun m a b -> Fun m (Either c a) (Either c b)+ right (Fun amb)= Fun $ \eitherCa -> case eitherCa of+ Right a -> Right <$> amb a+ Left c -> pure $ Left c+ {-# INLINE right #-}++{- | 'Monoidal' is 'Strong' 'Profunctor' that can be appended. It is+similar to 'Monoid's but for higher-kinded types.++@since 0.0.0.0+-}+class Strong p => Monoidal p where+ pappend :: p a b -> p c d -> p (a, c) (b, d)+ pempty :: p a a++-- | @since 0.0.0.0+instance Monoidal (->) where+ pappend :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)+ pappend ab cd (a, c) = (ab a, cd c)+ {-# INLINE pappend #-}++ pempty :: a -> a+ pempty = id+ {-# INLINE pempty #-}++-- | @since 0.0.0.0+instance (Applicative m) => Monoidal (Fun m) where+ pappend :: Fun m a b -> Fun m c d -> Fun m (a, c) (b, d)+ pappend (Fun amb) (Fun cmd) = Fun (\(a, c) -> liftA2 (,) (amb a) (cmd c))+ {-# INLINE pappend #-}++ pempty :: Fun m a a+ pempty = Fun (pure . id)+ {-# INLINE pempty #-}++{- | 'Optic' takes a connection from @a@ to @b@ (represented as a+value of type @p a b@) and returns a connection from @source@ to+@target@ (represented as a value of type @p source target@).++@+ +---> Profunctor+ |+ | +----> Final input+ | |+ | | +-> Final output+ | | |+ + + ++type Optic p source target a b+ + ++ | |+ Given input <--+ |+ |+ Given output <-------++@++@since 0.0.0.0+-}+type Optic p source target a b = p a b -> p source target+++{- $lenses++== Example++To understand better how to use this library lets look at some simple example.+Let's say we have the user and address data types in our system:++>>> :{+data Address = Address+ { addressCountry :: String+ , addressCity :: String+ , addressIndex :: String+ } deriving (Show)+:}++>>> :{+data User = User+ { userName :: String+ , userAge :: Int+ , userAddress :: Address+ } deriving (Show)+:}++We can easily get fields of the @User@ and @Address@ types, but+setting values is inconvenient (especially for nested records). To+solve this problem, we can use lenses — composable getters and+setters. 'Lens' is a value, so we need to define lenses for fields of+our data types first.++To create the lens for the @userName@ field we can use 'lens' function and+manually writing getter and setter function:++>>> :{+nameL :: Lens' User String+nameL = lens getter setter+ where+ getter :: User -> String+ getter = userName+ setter :: User -> String -> User+ setter user newName = user {userName = newName}+:}++In this manner, we can create other lenses for our @User@ data type.+Usually, lenses are one-liners, and we can define them easily using lambda-functions.++>>> :{+ageL :: Lens' User Int+ageL = lens userAge (\u new -> u {userAge = new})+:}++>>> :{+addressL :: Lens' User Address+addressL = lens userAddress (\u new -> u {userAddress = new})+:}++We want to have lenses for accessing @Adress@ fields inside @User@, so we want to have the following values:++@+countryL :: 'Lens'' User 'String'+cityL :: 'Lens'' User 'String'+indexL :: 'Lens'' User 'String'+@++/Note:/ for lenses as @countryL@, @cityL@ etc., we are using composition of the+lenses for the @userAddress@ field. If we have++>>> :{+addressCityL :: Lens' Address String+addressCityL = lens addressCity (\a new -> a {addressCity = new})+:}++then++>>> cityL = addressL . addressCityL++Let's say we have some sample user++>>> :{+address = Address+ { addressCountry = "UK"+ , addressCity = "London"+ , addressIndex = "XXX"+ }+user :: User+user = User+ { userName = "John"+ , userAge = 42+ , userAddress = address+ }+:}++To view the fields of the User data type we can use 'view' or '^.'++>>> view ageL user+42+>>> user ^. cityL+"London"++If we want to change any of the user's data, we should use 'set' or '.~'++>>> set nameL "Johnny" user+User {userName = "Johnny", userAge = 42, userAddress = Address {addressCountry = "UK", addressCity = "London", addressIndex = "XXX"}}+>>> user & cityL .~ "Bristol"+User {userName = "John", userAge = 42, userAddress = Address {addressCountry = "UK", addressCity = "Bristol", addressIndex = "XXX"}}++'over' or '%~' operator could be useful when, for example, you want to increase the age by one on the user's birthday:++>>> over ageL succ user+User {userName = "John", userAge = 43, userAddress = Address {addressCountry = "UK", addressCity = "London", addressIndex = "XXX"}}+>>> user & ageL %~ succ+User {userName = "John", userAge = 43, userAddress = Address {addressCountry = "UK", addressCity = "London", addressIndex = "XXX"}}+-}++{- | 'Lens' represents composable getters and setters.++'Lens' is an @'Optic' p@ with the 'Strong' constraint on the @p@ type variable.++@+ +---> Current object+ |+ | +-> Final object+ | |+ + ++type Lens source target a b+ + ++ | |+ Current field <--+ |+ |+ Final field <-------++@++@since 0.0.0.0+-}+type Lens source target a b = forall p . Strong p => Optic p source target a b++{- | The monomorphic lenses which don't change the type of the container (or of+the value inside). It has a 'Strong' constraint, and it can be used whenever a+getter or a setter is needed.++ * @a@ is the type of the value inside of structure+ * @source@ is the type of the whole structure++For most use-cases it's enought to use this 'Lens'' instead of more general 'Lens'.++@since 0.0.0.0+-}+type Lens' source a = Lens source source a a++{- | Sets the given value to the structure using a setter.++@since 0.0.0.0+-}+set :: (p ~ (->))+ => Optic p source target a b -- ^ 'Optic' that can be lens+ -> b -- ^ Value to set+ -> source -- ^ Object where we want to set value+ -> target -- ^ Resulting object with @b@ set+set abst = abst . const+{-# INLINE set #-}++{- | Applies the given function to the target.++@since 0.0.0.0+-}+over+ :: (p ~ (->))+ => Optic p source target a b -- ^ 'Optic' that can be lens+ -> (a -> b) -- ^ Field modification function+ -> source -- ^ Object where we want to set value+ -> target -- ^ Resulting object with the modified field+over = id+{-# INLINE over #-}++{- | Gets a value out of a structure using a getter.++@since 0.0.0.0+-}+view+ :: (p ~ Fun (Const a))+ => Optic p source target a b -- ^ 'Optic' that can be lens+ -> source -- ^ Object from which we want to get value+ -> a -- ^ Field of @source@+view opt = coerce (opt (Fun Const))+{-# INLINE view #-}+-- view opt = getConst . unFun (opt (Fun Const))+-- opt :: Fun (Const a) a b -> Fun (Const a) s t+-- opt :: (a -> Const a b) -> ( s -> Const a t)++{- | Creates 'Lens' from the getter and setter.++@since 0.0.0.0+-}+lens+ :: (source -> a) -- ^ Getter+ -> (source -> b -> target) -- ^ Setter+ -> Lens source target a b+lens getter setter = dimap (\s -> (s, getter s)) (uncurry setter) . second+{-# INLINE lens #-}++{- | The operator form of 'view' with the arguments flipped.++@since 0.0.0.0+-}+infixl 8 ^.+(^.) :: source -> Lens' source a -> a+s ^. l = view l s+{-# INLINE (^.) #-}++{- | The operator form of 'set'.++@since 0.0.0.0+-}+infixr 4 .~+(.~) :: Lens' source a -> a -> source -> source+(.~) = set+{-# INLINE (.~) #-}++{- | The operator form of 'over'.++@since 0.0.0.0+-}+infixr 4 %~+(%~) :: Lens' source a -> (a -> a) -> source -> source+(%~) = over+{-# INLINE (%~) #-}++{- | 'Lens'' for a tuple on the first argument.++>>> view fstL (42, "str")+42++@since 0.0.0.0+-}+fstL :: Lens (a, c) (b, c) a b+fstL = lens fst $ \(_, b) new -> (new, b)+{-# INLINE fstL #-}++{- | 'Lens'' for a tuple on the second argument.++>>> view sndL (42, "Hello")+"Hello"++@since 0.0.0.0+-}+sndL :: Lens (x, a) (x, b) a b+sndL = lens snd $ \(a, _) new -> (a, new)+{-# INLINE sndL #-}++{- $prisms+Prisms work with sum types.++== Example++Let's say we have the user data type in our system:++>>> :{+data Address = Address+ { addressCountry :: String+ , addressCity :: String+ } deriving (Show)+:}++>>> :{+data Payload+ = NamePayload String+ | IdPayload Int+ | AddressPayload Address+ deriving (Show)+:}++To create the prism for each constructor we can use 'prism'' function and+manually writing getter and setter function:++/NOTE:/ The naming convention for prisms is the following:++@+_ConstructorName+@++>>> :{+_NamePayload :: Prism' Payload String+_NamePayload = prism' construct match+ where+ match :: Payload -> Maybe String+ match p = case p of+ NamePayload name -> Just name+ _otherPayload -> Nothing+ construct :: String -> Payload+ construct = NamePayload+:}++In this manner, we can create other prisms for our @Payload@ data type.++>>> :{+_IdPayload :: Prism' Payload Int+_IdPayload = prism' IdPayload $ \p -> case p of+ IdPayload i -> Just i+ _otherPayload -> Nothing+:}++>>> :{+_AddressPayload :: Prism' Payload Address+_AddressPayload = prism' AddressPayload $ \p -> case p of+ AddressPayload a -> Just a+ _otherPayload -> Nothing+:}++Let's say we have some sample payload++>>> :{+payloadName :: Payload+payloadName = NamePayload "Some name"+:}++To view the fields of the @Payload@ data type we can use 'preview'++>>> preview _NamePayload payloadName+Just "Some name"+>>> preview _IdPayload payloadName+Nothing++If we want to change any of the data, we should use 'set' or '.~' (just like in lenses)++>>> set _NamePayload "Johnny" payloadName+NamePayload "Johnny"+>>> set _IdPayload 3 payloadName+NamePayload "Some name"++Note, that you can easily compose lenses and prisms together:++>>> :{+address = Address+ { addressCountry = "UK"+ , addressCity = "London"+ }+:}++>>> :{+addressCityL :: Lens' Address String+addressCityL = lens addressCity (\a new -> a {addressCity = new})+:}++>>> :{+payloadAddress :: Payload+payloadAddress = AddressPayload address+:}++>>> set _AddressPayload (address & addressCityL .~ "Bristol") payloadAddress+AddressPayload (Address {addressCountry = "UK", addressCity = "Bristol"})+-}++{- | 'Prism' represents composable constructors and deconstructors.++'Prism' is an @'Optic' p@ with 'Choice' constraint on the @p@ type+variable.++@+ +---> Current object+ |+ | +-> Final object+ | |+ + ++ type Prism source target a b+ + ++ | |+ Field in current constructor <--+ |+ |+Field in final constructor <-------++@++@since 0.0.0.0+-}+type Prism source target a b = forall p . Choice p => Optic p source target a b++{- | The monomorphic prisms which don't change the type of the container (or of+the value inside).++ * @a@ is the value inside the particular constructor+ * @source@ is some sum type++@since 0.0.0.0+-}+type Prism' source a = Prism source source a a++{- | Newtype around function @a -> r@. It's called /forget/ because it+forgets about its last type variable.++@since 0.0.0.0+-}+newtype Forget r a b = Forget+ { unForget :: a -> r+ }++-- | @since 0.0.0.0+instance Functor (Forget r x) where+ fmap :: (a -> b) -> Forget r x a -> Forget r x b+ fmap _ = coerce++-- | @since 0.0.0.0+instance Profunctor (Forget r) where+ dimap :: (a -> b) -> (c -> d) -> Forget r b c -> Forget r a d+ dimap ab _cd (Forget br) = Forget (br . ab)+ {-# INLINE dimap #-}++-- | @since 0.0.0.0+instance Strong (Forget r) where+ first :: Forget r a b -> Forget r (a, c) (b, c)+ first (Forget ar) = Forget (ar . fst)+ {-# INLINE first #-}++ second :: Forget r a b -> Forget r (c, a) (c, b)+ second (Forget ar) = Forget (ar . snd)+ {-# INLINE second #-}++-- | @since 0.0.0.0+instance Monoid r => Choice (Forget r) where+ left :: Forget r a b -> Forget r (Either a c) (Either b c)+ left (Forget ar) = Forget (either ar (const mempty))+ {-# INLINE left #-}++ right :: Forget r a b -> Forget r (Either c a) (Either c b)+ right (Forget ar) = Forget (either (const mempty) ar)+ {-# INLINE right #-}++-- | @since 0.0.0.0+instance (Monoid r) => Monoidal (Forget r) where+ pappend :: Forget r a b -> Forget r c d -> Forget r (a, c) (b, d)+ pappend (Forget ar) (Forget cr) = Forget (\(a, c) -> ar a <> cr c)+ {-# INLINE pappend #-}++ pempty :: Forget r a a+ pempty = Forget (const mempty)+ {-# INLINE pempty #-}++{- | Match a value from @source@ type.++@since 0.0.0.0+-}+preview+ :: forall a source p+ . (p ~ Forget (First a))+ => Optic p source source a a -- ^ 'Optic' that can be prism+ -> source -- ^ Object (possible sum type)+ -> Maybe a -- ^ Value of type @a@ from a specific constructor+preview paapss = coerce (paapss wrap)+ where+ wrap :: Forget (First a) a a+ wrap = coerce @(a -> Maybe a) @(Forget (First a) a a) Just+ {-# INLINE wrap #-}+{-# INLINE preview #-}+-- preview paapss = getFirst . unForget (paapss (Forget (First . Just)))+-- paapss :: Forget (First a) a a -> Forget (First a) source source+-- paapss :: (a -> First a) -> source -> First a+-- paapss :: (a -> Maybe a) -> source -> Maybe a++{- | Create 'Prism' from constructor and matching function.++@since 0.0.0.0+-}+prism+ :: (b -> target) -- ^ Constructor+ -> (source -> Either target a) -- ^ Matching function+ -> Prism source target a b+-- prism :: (b -> target) -> (source -> Either target a) -> p a b -> p source target+prism ctor match = dimap match (either id ctor) . right+{-# INLINE prism #-}++{- | Create monomorphic 'Prism'' from constructor and matching function.++@since 0.0.0.0+-}+prism'+ :: (a -> source) -- ^ Constructor+ -> (source -> Maybe a) -- ^ Matching function+ -> Prism' source a+prism' ctor match = prism ctor (\s -> maybe (Left s) Right $ match s)+{-# INLINE prism' #-}++{- | 'Prism' for a 'Just' of 'Maybe'.++>>> preview _Just (Just 42)+Just 42++>>> preview _Just Nothing+Nothing++@since 0.0.0.0+-}+_Just :: Prism (Maybe a) (Maybe b) a b+_Just = prism Just $ \case+ Just a -> Right a+ Nothing -> Left Nothing+{-# INLINE _Just #-}+++{- | 'Prism' for a 'Left' of 'Either'.++>>> preview _Left (Left 42)+Just 42++>>> preview _Left (Right "Hello")+Nothing++@since 0.0.0.0+-}+_Left :: Prism (Either a x) (Either b x) a b+_Left = prism Left $ \case+ Left l -> Right l+ Right r -> Left $ Right r+{-# INLINE _Left #-}++{- | 'Prism' for a 'Left' of 'Either'.++>>> preview _Right (Left 42)+Nothing++>>> preview _Right (Right "Hello")+Just "Hello"++@since 0.0.0.0+-}+_Right :: Prism (Either x a) (Either x b) a b+_Right = prism Right $ \case+ Right a -> Right a+ Left x -> Left $ Left x+{-# INLINE _Right #-}+++{- | 'Traversal' provides composable ways to visit different parts of+a data structure.++'Traversal' is an @'Optic' p@ with the 'Choice' and 'Monoidal'+constraints on the @p@ type variable.++@+ +---> Current collection+ |+ | +-> Final collection+ | |+ + ++type Traversal source target a b+ + ++ | |+ Current element <--+ |+ |+ Final element <-------++@++@since 0.0.0.0+-}+type Traversal source target a b+ = forall p+ . (Choice p, Monoidal p)+ => Optic p source target a b++{- | Traverse a data structure using given 'Traversal'.++>>> traverseOf eachPair putStrLn ("Hello", "World!")+Hello+World!+((),())++@since 0.0.0.0+-}+traverseOf+ :: (Applicative f, p ~ Fun f)+ => Optic p source target a b -- ^ 'Optic' that can be a traversal+ -> (a -> f b) -- ^ Traversing function+ -> source -- ^ Data structure to traverse+ -> f target -- ^ Traversing result+traverseOf pabPst aFb = unFun (pabPst (Fun aFb))+-- pabPst :: Fun f a b -> Fun f source target+-- pabPst :: (a -> f b) -> Fun f source target++{- | 'Traversal' for a pair of same type elements.++>>> over eachPair (+ 1) (3, 7)+(4,8)++@since 0.0.0.0+-}+eachPair :: Traversal (a, a) (b, b) a b+eachPair pab = pappend pab pab++{- | 'Traversal' for a 'Maybe'.++>>> over eachMaybe (+ 1) (Just 3)+Just 4+>>> over eachMaybe (+ 1) Nothing+Nothing++@since 0.0.0.0+-}+eachMaybe :: Traversal (Maybe a) (Maybe b) a b+eachMaybe pab = dimap maybeToEither eitherToMaybe (left pab)+ where+ maybeToEither :: Maybe a -> Either a ()+ maybeToEither = \case+ Just a -> Left a+ Nothing -> Right ()++ eitherToMaybe :: Either a () -> Maybe a+ eitherToMaybe = \case+ Left a -> Just a+ Right () -> Nothing++{- | 'Traversal' for lists.++>>> over eachList (+ 1) [1..5]+[2,3,4,5,6]+>>> over eachList (+ 1) []+[]++@since 0.0.0.0+-}+eachList :: Traversal [a] [b] a b+eachList pab = dimap listToEither eitherToList $ left $ pappend pab (eachList pab)+ where+ listToEither :: [a] -> Either (a, [a]) ()+ listToEither = \case+ [] -> Right ()+ x:xs -> Left (x, xs)++ eitherToList :: Either (a, [a]) () -> [a]+ eitherToList = \case+ Right () -> []+ Left (x, xs) -> x:xs
+ test/Doctest.hs view
@@ -0,0 +1,15 @@+module Main (main) where++import Test.DocTest (doctest)+++main :: IO ()+main = doctest+ $ "-XLambdaCase"+ : "-XInstanceSigs"+ : "-XScopedTypeVariables"+ : "-XTupleSections"+ : "-XTypeApplications"+ :+ [ "src/Prolens.hs"+ ]
+ test/Spec.hs view
@@ -0,0 +1,15 @@+module Main (main) where++import Test.Hspec (hspec)++import Test.Prolens (unitSpecs)+import Test.Prolens.Inspection (inspectionSpecs)+import Test.Prolens.Property (lensPropertySpecs, typeclassesPropertySpecs)+++main :: IO ()+main = hspec $ do+ unitSpecs+ inspectionSpecs+ lensPropertySpecs+ typeclassesPropertySpecs
+ test/Test/Data.hs view
@@ -0,0 +1,131 @@+module Test.Data+ ( -- * Lenses+ Haskeller (..)+ , nameL+ , knowledgeL++ , Knowledge (..)+ , syntaxL++ , me++ -- * Prisms+ , Grade (..)+ , _Mark++ , gradeMark+ , gradeOther++ -- * Generators+ , genFun+ , genFunction+ , genHaskeller+ , genInt+ , genKnowledge+ , genName+ ) where++import Test.Hspec.Hedgehog (MonadGen)++import Prolens (Fun (..), Lens', Prism', lens, prism')++import qualified Hedgehog.Gen as Gen+import qualified Hedgehog.Range as Range+++data Haskeller = Haskeller+ { haskellerName :: String+ , haskellerExperience :: Int+ , haskellerKnowledge :: Knowledge+ } deriving stock (Show, Eq)++data Knowledge = Knowledge+ { kSyntax :: Bool+ , kMonads :: Bool+ , kLens :: Bool+ , kTypeLevelMagic :: Bool+ , kNix :: Bool+ } deriving stock (Show, Eq)++me :: Haskeller+me = Haskeller+ { haskellerName = "Veronika"+ , haskellerExperience = 2+ , haskellerKnowledge = Knowledge+ { kSyntax = True+ , kMonads = True+ , kLens = True+ , kTypeLevelMagic = True+ , kNix = False+ }+ }++nameL :: Lens' Haskeller String+nameL = lens haskellerName (\h new -> h { haskellerName = new })+{-# INLINE nameL #-}++knowledgeL :: Lens' Haskeller Knowledge+knowledgeL = lens haskellerKnowledge (\h new -> h { haskellerKnowledge = new })+{-# INLINE knowledgeL #-}++syntaxL :: Lens' Knowledge Bool+syntaxL = lens kSyntax (\k new -> k { kSyntax = new })+{-# INLINE syntaxL #-}++data Grade+ = Mark Int+ | Other String+ deriving stock (Show, Eq)++_Mark :: Prism' Grade Int+_Mark = prism' Mark $ \case+ Mark a -> Just a+ _other -> Nothing+{-# INLINE _Mark #-}++gradeMark :: Grade+gradeMark = Mark 5++gradeOther :: Grade+gradeOther = Other "Satisfactory"++-- Generators++genKnowledge :: (MonadGen m) => m Knowledge+genKnowledge = do+ kSyntax <- Gen.bool+ kMonads <- Gen.bool+ kLens <- Gen.bool+ kTypeLevelMagic <- Gen.bool+ kNix <- Gen.bool+ pure Knowledge{..}++genHaskeller :: (MonadGen m) => m Haskeller+genHaskeller = do+ haskellerName <- genName+ haskellerExperience <- Gen.int $ Range.linear 0 50+ haskellerKnowledge <- genKnowledge+ pure Haskeller{..}++genName :: MonadGen m => m String+genName = Gen.string (Range.linear 1 50) Gen.alphaNum++genInt :: MonadGen m => m Int+genInt = Gen.enumBounded++genFunction :: MonadGen m => m (Int -> Int)+genFunction = genInt >>= \n -> Gen.element+ [ id+ , const n+ , (+ n)+ , (* n)+ , subtract n+ , \x -> if x >= n then 1 else 0+ ]++genFun :: MonadGen m => m (Fun Maybe Int Int)+genFun = genFunction >>= \f -> Gen.element $ map Fun+ [ Just+ , const Nothing+ , Just . f+ ]
+ test/Test/Prolens.hs view
@@ -0,0 +1,43 @@+module Test.Prolens+ ( unitSpecs+ ) where++import Data.Function ((&))+import Test.Hspec (Spec, describe, it, shouldBe)++import Prolens (preview, set, view, (.~), (^.))+import Test.Data (Grade (..), gradeMark, gradeOther, me, nameL, _Mark)+++unitSpecs :: Spec+unitSpecs = describe "Prolens unit tests" $ do+ lensSpecs+ prismSpecs+++lensSpecs :: Spec+lensSpecs = describe "Lenses" $ do+ describe "getter" $ do+ it "should get name" $+ view nameL me `shouldBe` "Veronika"+ it "should get name with ^." $+ (me ^. nameL) `shouldBe` "Veronika"+ describe "setter" $ do+ it "should set name" $+ view nameL (set nameL "Dmitrii" me) `shouldBe` "Dmitrii"+ it "should set name with .~" $+ (me & nameL .~ "Dmitrii") ^. nameL `shouldBe` "Dmitrii"+++prismSpecs :: Spec+prismSpecs = describe "Prisms" $ do+ describe "preview" $ do+ it "should get mark" $+ preview _Mark gradeMark `shouldBe` Just 5+ it "should not get mark" $+ preview _Mark gradeOther `shouldBe` Nothing+ describe "set" $ do+ it "should get mark" $+ set _Mark 4 gradeMark `shouldBe` Mark 4+ it "should not get mark" $+ set _Mark 4 gradeOther `shouldBe` gradeOther
+ test/Test/Prolens/Inspection.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE TemplateHaskell #-}++{- | Performance tests for @prolens@. Uses the @inspection-testing@+library to make sure that lenses are as efficient as manual record+getters and update syntax.+-}++module Test.Prolens.Inspection+ ( inspectionSpecs+ ) where++import Test.Hspec (Spec, describe, it, shouldSatisfy, xit)+import Test.Inspection (Result (..), hasNoTypeClasses, inspectTest, (===))++import Prolens (preview, set, view)+import Test.Data (Grade (..), Haskeller (..), Knowledge (..), _Mark, knowledgeL, nameL, syntaxL)+++setNameViaLens :: Haskeller -> Haskeller+setNameViaLens = set nameL "Dmitrii"++setNameManually :: Haskeller -> Haskeller+setNameManually h = h { haskellerName = "Dmitrii" }++getNameViaLens :: Haskeller -> String+getNameViaLens = view nameL++getNameManually :: Haskeller -> String+getNameManually (Haskeller name _ _) = name++setSyntaxViaLens :: Haskeller -> Haskeller+setSyntaxViaLens = set (knowledgeL . syntaxL) True++setSyntaxManually :: Haskeller -> Haskeller+setSyntaxManually h = h { haskellerKnowledge = (haskellerKnowledge h) { kSyntax = True } }++getSyntaxViaLens :: Haskeller -> Bool+getSyntaxViaLens = view (knowledgeL . syntaxL)++getSyntaxManually :: Haskeller -> Bool+getSyntaxManually (Haskeller _ _ (Knowledge syntax _ _ _ _)) = syntax++inspectionSpecs :: Spec+inspectionSpecs = describe "Performance Inspection Testing" $ do+ lensSpecs+ prismSpecs++lensSpecs :: Spec+lensSpecs = describe "Lens" $ do+ describe "set" $ do+ it "setting single via lens is efficient as manual record update" $+ $(inspectTest $ 'setNameViaLens === 'setNameManually) `shouldSatisfy` isSuccess+ it "setting single via lens doesn't have intermediate typeclasses" $+ $(inspectTest $ hasNoTypeClasses 'setNameViaLens) `shouldSatisfy` isSuccess+ it "setting composition via lens is efficient as manual record update" $+ $(inspectTest $ 'setSyntaxViaLens === 'setSyntaxManually) `shouldSatisfy` isSuccess+ it "setting composition via lens doesn't have intermediate typeclasses" $+ $(inspectTest $ hasNoTypeClasses 'setSyntaxViaLens) `shouldSatisfy` isSuccess+ describe "view" $ do+ it "getting single via lens is efficient as plain record function" $+ $(inspectTest $ 'getNameViaLens === 'getNameManually) `shouldSatisfy` isSuccess+ it "getting single via lens doesn't have intermediate typeclasses" $+ $(inspectTest $ hasNoTypeClasses 'getNameViaLens) `shouldSatisfy` isSuccess+ it "getting composition via lens is efficient as plain record function" $+ $(inspectTest $ 'getSyntaxViaLens === 'getSyntaxManually) `shouldSatisfy` isSuccess+ it "getting composition via lens doesn't have intermediate typeclasses" $+ $(inspectTest $ hasNoTypeClasses 'getSyntaxViaLens) `shouldSatisfy` isSuccess++matchMarkPrism :: Grade -> Maybe Int+matchMarkPrism = preview _Mark++matchMarkManual :: Grade -> Maybe Int+matchMarkManual grade = case grade of+ Mark n -> Just n+ _other -> Nothing++prismSpecs :: Spec+prismSpecs = describe "Prism" $ do+ describe "preview" $ do+ xit "preview _Ctor x ≡ case (Ctor _) of" $+ $(inspectTest $ 'matchMarkPrism === 'matchMarkManual) `shouldSatisfy` isSuccess++-- Helper functions++isSuccess :: Result -> Bool+isSuccess (Success _) = True+isSuccess (Failure _) = False
+ test/Test/Prolens/Property.hs view
@@ -0,0 +1,68 @@+module Test.Prolens.Property+ ( lensPropertySpecs+ , typeclassesPropertySpecs+ ) where++import Test.Hspec (Spec, describe, it)+import Test.Hspec.Hedgehog (PropertyT, forAll, forAllWith, hedgehog, (===))++import Prolens+import Test.Data (genFun, genFunction, genHaskeller, genInt, genName, nameL)+++lensPropertySpecs :: Spec+lensPropertySpecs = describe "Lens Laws" $ do+ it "view lens (set lens value source) ≡ value" $ hedgehog $ do+ source <- forAll genHaskeller+ value <- forAll genName+ view nameL (set nameL value source) === value+ it "set lens (view lens source) source ≡ source" $ hedgehog $ do+ source <- forAll genHaskeller+ set nameL (view nameL source) source === source+ it "set lens valueNew (set lens value source) ≡ set lens valueNew source" $ hedgehog $ do+ source <- forAll genHaskeller+ value <- forAll genName+ valueNew <- forAll genName+ set nameL valueNew (set nameL value source) === set nameL valueNew source++typeclassesPropertySpecs :: Spec+typeclassesPropertySpecs = describe "Class Laws" -- $ do+ profunctorsSpec++profunctorsSpec :: Spec+profunctorsSpec = describe "Profunctor" $ do+ describe "(->)" $ do+ it "Identity: dimap id id ≡ id" $ hedgehog $ do+ f <- forAllWith (const "f") genFunction+ x <- forAll genInt+ dimap id id f x === f x+ it "Composition: dimap (ab . bc) (yz . xy) ≡ dimap bc yz . dimap ab xy" $ hedgehog $ do++ f <- forAllWith (const "f") genFunction+ ab <- forAllWith (const "ab") genFunction+ bc <- forAllWith (const "bc") genFunction+ xy <- forAllWith (const "xy") genFunction+ yz <- forAllWith (const "xy") genFunction++ n <- forAll genInt+ dimap (ab . bc) (yz . xy) f n === (dimap bc yz . dimap ab xy) f n+ describe "Fun" $ do+ it "Identity: dimap id id ≡ id" $ hedgehog $ do+ f <- forAllWith (const "f") genFun+ eqFun (dimap id id f) f+ it "Composition: dimap (ab . bc) (yz . xy) ≡ dimap bc yz . dimap ab xy" $ hedgehog $ do++ f <- forAllWith (const "f") genFun+ ab <- forAllWith (const "ab") genFunction+ bc <- forAllWith (const "bc") genFunction+ xy <- forAllWith (const "xy") genFunction+ yz <- forAllWith (const "xy") genFunction++ eqFun+ (dimap (ab . bc) (yz . xy) f)+ (dimap bc yz $ dimap ab xy f)++eqFun :: Fun Maybe Int Int -> Fun Maybe Int Int -> PropertyT IO ()+eqFun fun1 fun2 = do+ x <- forAll genInt+ unFun fun1 x === unFun fun2 x