Skip to content

Commit 0db132f

Browse files
authored
Merge pull request #4701 from phadej/lensify
Introduce Distribution.Compat.Lens
2 parents 8549993 + 6c448b6 commit 0db132f

File tree

16 files changed

+466
-219
lines changed

16 files changed

+466
-219
lines changed

Cabal/Cabal.cabal

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ library
265265
Language.Haskell.Extension
266266
Distribution.Compat.Binary
267267

268+
-- Parsec parser relatedmodules
268269
build-depends:
269270
transformers,
270271
parsec >= 3.1.9 && <3.2
@@ -283,6 +284,13 @@ library
283284
Distribution.Parsec.Types.FieldDescr
284285
Distribution.Parsec.Types.ParseResult
285286

287+
-- Lens functionality
288+
exposed-modules:
289+
Distribution.Compat.Lens
290+
Distribution.Types.BuildInfo.Lens
291+
Distribution.Types.PackageDescription.Lens
292+
Distribution.Types.GenericPackageDescription.Lens
293+
286294
other-modules:
287295
Distribution.Backpack.PreExistingComponent
288296
Distribution.Backpack.ReadyComponent

Cabal/Distribution/Compat/Lens.hs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
{-# LANGUAGE RankNTypes #-}
2+
-- | This module provides very basic lens functionality, without extra dependencies.
3+
--
4+
-- For the documentation of the combinators see <http://hackage.haskell.org/package/lens lens> package.
5+
-- This module uses the same vocabulary.
6+
module Distribution.Compat.Lens (
7+
-- * Types
8+
Lens,
9+
Lens',
10+
Traversal,
11+
Traversal',
12+
-- ** rank-1 types
13+
Getting,
14+
ASetter,
15+
-- * Getter
16+
view,
17+
-- * Setter
18+
set,
19+
over,
20+
-- * Fold
21+
toDListOf,
22+
toListOf,
23+
toSetOf,
24+
-- * Common lenses
25+
_1, _2,
26+
-- * Operators
27+
(&),
28+
(.~), (%~),
29+
(?~),
30+
-- * Cabal developer info
31+
-- $development
32+
) where
33+
34+
import Prelude()
35+
import Distribution.Compat.Prelude
36+
37+
import Control.Applicative (Const (..))
38+
import Data.Functor.Identity (Identity (..))
39+
40+
import qualified Distribution.Compat.DList as DList
41+
import qualified Data.Set as Set
42+
43+
-------------------------------------------------------------------------------
44+
-- Types
45+
-------------------------------------------------------------------------------
46+
47+
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
48+
type Traversal s t a b = forall f. Applicative f => (a -> f b) -> s -> f t
49+
50+
type Lens' s a = forall f. Functor f => (a -> f a) -> s -> f s
51+
type Traversal' s a = forall f. Applicative f => (a -> f a) -> s -> f s
52+
53+
type Getting r s a = (a -> Const r a) -> s -> Const r s
54+
type ASetter s t a b = (a -> Identity b) -> s -> Identity t
55+
56+
-------------------------------------------------------------------------------
57+
-- Getter
58+
-------------------------------------------------------------------------------
59+
60+
view :: s -> Getting a s a -> a
61+
view s l = getConst (l Const s)
62+
63+
-------------------------------------------------------------------------------
64+
-- Setter
65+
-------------------------------------------------------------------------------
66+
67+
set :: ASetter s t a b -> b -> s -> t
68+
set l x = over l (const x)
69+
70+
over :: ASetter s t a b -> (a -> b) -> s -> t
71+
over l f s = runIdentity (l (\x -> Identity (f x)) s)
72+
73+
-------------------------------------------------------------------------------
74+
-- Fold
75+
-------------------------------------------------------------------------------
76+
77+
toDListOf :: Getting (DList.DList a) s a -> s -> DList.DList a
78+
toDListOf l s = getConst (l (\x -> Const (DList.singleton x)) s)
79+
80+
toListOf :: Getting (DList.DList a) s a -> s -> [a]
81+
toListOf l = DList.runDList . toDListOf l
82+
83+
toSetOf :: Getting (Set.Set a) s a -> s -> Set.Set a
84+
toSetOf l s = getConst (l (\x -> Const (Set.singleton x)) s)
85+
86+
-------------------------------------------------------------------------------
87+
-- Lens
88+
-------------------------------------------------------------------------------
89+
90+
{-
91+
lens :: (s -> a) -> (s -> a -> s) -> Lens' s a
92+
lens sa sbt afb s = sbt s <$> afb (sa s)
93+
-}
94+
95+
-------------------------------------------------------------------------------
96+
-- Common
97+
-------------------------------------------------------------------------------
98+
99+
_1 :: Lens (a, c) (b, c) a b
100+
_1 f (a, c) = flip (,) c <$> f a
101+
102+
_2 :: Lens (c, a) (c, b) a b
103+
_2 f (c, a) = (,) c <$> f a
104+
105+
-------------------------------------------------------------------------------
106+
-- Operators
107+
-------------------------------------------------------------------------------
108+
109+
110+
-- | '&' is a reverse application operator
111+
(&) :: a -> (a -> b) -> b
112+
(&) = flip ($)
113+
{-# INLINE (&) #-}
114+
infixl 1 &
115+
116+
infixr 4 .~, %~, ?~
117+
118+
(.~) :: ASetter s t a b -> b -> s -> t
119+
(.~) = set
120+
{-# INLINE (.~) #-}
121+
122+
(?~) :: ASetter s t a (Maybe b) -> b -> s -> t
123+
l ?~ b = set l (Just b)
124+
{-# INLINE (?~) #-}
125+
126+
(%~) :: ASetter s t a b -> (a -> b) -> s -> t
127+
(%~) = over
128+
{-# INLINE (%~) #-}
129+
130+
-------------------------------------------------------------------------------
131+
-- Documentation
132+
-------------------------------------------------------------------------------
133+
134+
-- $development
135+
--
136+
-- We cannot depend on @template-haskell@, because Cabal is a boot library.
137+
-- This fact makes defining optics a manual task. Here is a small recipe to
138+
-- make the process less tedious.
139+
--
140+
-- First start a repl with proper-lens dependency
141+
--
142+
-- > cabal new-repl Cabal:lib:Cabal ???
143+
--
144+
-- or
145+
--
146+
-- > stack ghci Cabal:lib --package lens
147+
--
148+
-- Then enable Template Haskell and the dumping of splices:
149+
--
150+
-- > :set -XTemplateHaskell -ddump-slices
151+
--
152+
-- Now we can derive lenses, load appropriate modules:
153+
--
154+
-- > :m Control.Lens Distribution.PackageDescription
155+
--
156+
-- and let Template Haskell do the job:
157+
--
158+
-- > ; makeLensesWith (lensRules & lensField .~ mappingNamer return) ''GenericPackageDescription
159+
--
160+
-- At this point, we will get unfancy splices looking like
161+
--
162+
-- @
163+
-- condBenchmarks ::
164+
-- 'Lens'' GenericPackageDescription [(UnqualComponentName,
165+
-- CondTree ConfVar [Dependency] Benchmark)]
166+
-- condBenchmarks
167+
-- f_a2UEg
168+
-- (GenericPackageDescription x1_a2UEh
169+
-- x2_a2UEi
170+
-- x3_a2UEj
171+
-- x4_a2UEk
172+
-- x5_a2UEl
173+
-- x6_a2UEm
174+
-- x7_a2UEn
175+
-- x8_a2UEo)
176+
-- = fmap
177+
-- (\\ y1_a2UEp
178+
-- -> GenericPackageDescription
179+
-- x1_a2UEh
180+
-- x2_a2UEi
181+
-- x3_a2UEj
182+
-- x4_a2UEk
183+
-- x5_a2UEl
184+
-- x6_a2UEm
185+
-- x7_a2UEn
186+
-- y1_a2UEp)
187+
-- (f_a2UEg x8_a2UEo)
188+
-- {-\# INLINE condBenchmarks \#-}
189+
-- @
190+
--
191+
-- yet they can be cleaned up with e.g. VIM magic regexp and @hindent@:
192+
--
193+
-- > :%s/\v(\w+)_\w+/\1/g
194+
-- > :%!hindent --indent-size 4 --line-length 200
195+
--
196+
-- Resulting into
197+
--
198+
-- @
199+
-- condBenchmarks :: 'Lens'' GenericPackageDescription [(UnqualComponentName, CondTree ConfVar [Dependency] Benchmark)]
200+
-- condBenchmarks f (GenericPackageDescription x1 x2 x3 x4 x5 x6 x7 x8) =
201+
-- fmap (\\y1 -> GenericPackageDescription x1 x2 x3 x4 x5 x6 x7 y1) (f x8)
202+
-- {-\# INLINE condBenchmarks \#-}
203+
-- @

Cabal/Distribution/PackageDescription/Check.hs

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import Distribution.License
4545
import Distribution.Simple.BuildPaths (autogenPathsModuleName)
4646
import Distribution.Simple.BuildToolDepends
4747
import Distribution.Simple.CCompiler
48-
import Distribution.Types.BuildInfo
4948
import Distribution.Types.ComponentRequestedSpec
5049
import Distribution.Types.CondTree
5150
import Distribution.Types.Dependency
@@ -60,7 +59,6 @@ import Distribution.Text
6059
import Distribution.Utils.Generic (isAscii)
6160
import Language.Haskell.Extension
6261

63-
import Control.Applicative (Const (..))
6462
import Control.Monad (mapM)
6563
import qualified Data.ByteString.Lazy as BS
6664
import Data.List (group)
@@ -80,6 +78,11 @@ import System.FilePath.Windows as FilePath.Windows
8078

8179
import qualified Data.Set as Set
8280

81+
import Distribution.Compat.Lens
82+
import qualified Distribution.Types.BuildInfo.Lens as L
83+
import qualified Distribution.Types.PackageDescription.Lens as L
84+
import qualified Distribution.Types.GenericPackageDescription.Lens as L
85+
8386
-- | Results of some kind of failed package check.
8487
--
8588
-- There are a range of severities, from merely dubious to totally insane.
@@ -1626,23 +1629,17 @@ checkUnusedFlags gpd
16261629
s = commaSep . map unFlagName . Set.toList
16271630

16281631
declared :: Set.Set FlagName
1629-
declared = Set.fromList $ map flagName $ genPackageFlags gpd
1632+
declared = toSetOf (L.genPackageFlags . traverse . L.flagName) gpd
16301633

16311634
used :: Set.Set FlagName
1632-
used = Set.fromList $ DList.runDList $ getConst $
1633-
(traverse . traverseCondTreeV) tellFlag (condLibrary gpd) *>
1634-
(traverse . _2 . traverseCondTreeV) tellFlag (condSubLibraries gpd) *>
1635-
(traverse . _2 . traverseCondTreeV) tellFlag (condForeignLibs gpd) *>
1636-
(traverse . _2 . traverseCondTreeV) tellFlag (condExecutables gpd) *>
1637-
(traverse . _2 . traverseCondTreeV) tellFlag (condTestSuites gpd) *>
1638-
(traverse . _2 . traverseCondTreeV) tellFlag (condBenchmarks gpd)
1639-
1640-
_2 :: Functor f => (a -> f b) -> (c, a) -> f (c, b)
1641-
_2 f (c, a) = (,) c <$> f a
1642-
1643-
tellFlag :: ConfVar -> Const (DList.DList FlagName) ConfVar
1644-
tellFlag (Flag fn) = Const (DList.singleton fn)
1645-
tellFlag _ = Const mempty
1635+
used = mconcat
1636+
[ toSetOf (L.condLibrary . traverse . traverseCondTreeV . L._Flag) gpd
1637+
, toSetOf (L.condSubLibraries . traverse . _2 . traverseCondTreeV . L._Flag) gpd
1638+
, toSetOf (L.condForeignLibs . traverse . _2 . traverseCondTreeV . L._Flag) gpd
1639+
, toSetOf (L.condExecutables . traverse . _2 . traverseCondTreeV . L._Flag) gpd
1640+
, toSetOf (L.condTestSuites . traverse . _2 . traverseCondTreeV . L._Flag) gpd
1641+
, toSetOf (L.condBenchmarks . traverse . _2 . traverseCondTreeV . L._Flag) gpd
1642+
]
16461643

16471644
checkUnicodeXFields :: GenericPackageDescription -> [PackageCheck]
16481645
checkUnicodeXFields gpd
@@ -1657,23 +1654,10 @@ checkUnicodeXFields gpd
16571654
nonAsciiXFields = [ n | (n, _) <- xfields, any (not . isAscii) n ]
16581655

16591656
xfields :: [(String,String)]
1660-
xfields = DList.runDList $ getConst $
1661-
tellXFieldsPD (packageDescription gpd) *>
1662-
(traverse . traverse . buildInfo_) tellXFields (condLibrary gpd) *>
1663-
(traverse . _2 . traverse . buildInfo_) tellXFields (condSubLibraries gpd) *>
1664-
(traverse . _2 . traverse . buildInfo_) tellXFields (condForeignLibs gpd) *>
1665-
(traverse . _2 . traverse . buildInfo_) tellXFields (condExecutables gpd) *>
1666-
(traverse . _2 . traverse . buildInfo_) tellXFields (condTestSuites gpd) *>
1667-
(traverse . _2 . traverse . buildInfo_) tellXFields (condBenchmarks gpd)
1668-
1669-
tellXFields :: BuildInfo -> Const (DList.DList (String, String)) BuildInfo
1670-
tellXFields bi = Const (DList.fromList $ customFieldsBI bi)
1671-
1672-
tellXFieldsPD :: PackageDescription -> Const (DList.DList (String, String)) PackageDescription
1673-
tellXFieldsPD pd = Const (DList.fromList $ customFieldsPD pd)
1674-
1675-
_2 :: Functor f => (a -> f b) -> (c, a) -> f (c, b)
1676-
_2 f (c, a) = (,) c <$> f a
1657+
xfields = DList.runDList $ mconcat
1658+
[ toDListOf (L.packageDescription . L.customFieldsPD . traverse) gpd
1659+
, toDListOf (L.buildInfos . L.customFieldsBI . traverse) gpd
1660+
]
16771661

16781662
checkDevelopmentOnlyFlagsBuildInfo :: BuildInfo -> [PackageCheck]
16791663
checkDevelopmentOnlyFlagsBuildInfo bi =

Cabal/Distribution/PackageDescription/Parsec.hs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ import System.Directory
6565
import qualified Text.Parsec as P
6666
import qualified Text.Parsec.Error as P
6767

68+
import Distribution.Compat.Lens
69+
import qualified Distribution.Types.GenericPackageDescription.Lens as L
70+
import qualified Distribution.Types.PackageDescription.Lens as L
71+
6872
-- ---------------------------------------------------------------
6973
-- Parsing
7074

@@ -282,10 +286,7 @@ parseGenericPackageDescription' lexWarnings fs = do
282286

283287
| name == "custom-setup" && null args = do
284288
sbi <- parseFields setupBInfoFieldDescrs warnUnrec mempty fields
285-
let pd = packageDescription gpd
286-
-- TODO: what if already defined?
287-
let gpd' = gpd { packageDescription = pd { setupBuildInfo = Just sbi } }
288-
pure gpd'
289+
pure $ gpd & L.packageDescription . L.setupBuildInfo ?~ sbi
289290

290291
| name == "source-repository" = do
291292
kind <- case args of
@@ -298,16 +299,15 @@ parseGenericPackageDescription' lexWarnings fs = do
298299
parseFailure pos $ "Invalid source-repository kind " ++ show args
299300
pure RepoHead
300301
sr <- parseFields sourceRepoFieldDescrs warnUnrec (emptySourceRepo kind) fields
301-
-- I want lens
302-
let pd = packageDescription gpd
303-
let srs = sourceRepos pd
304-
let gpd' = gpd { packageDescription = pd { sourceRepos = srs ++ [sr] } }
305-
pure gpd'
302+
303+
pure $ gpd & L.packageDescription . L.sourceRepos %~ snoc sr
306304

307305
| otherwise = do
308306
parseWarning pos PWTUnknownSection $ "Ignoring section: " ++ show name
309307
pure gpd
310308

309+
snoc x xs = xs ++ [x]
310+
311311
newSyntaxVersion :: Version
312312
newSyntaxVersion = mkVersion [1, 2]
313313

Cabal/Distribution/Types/Benchmark.hs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import Distribution.Types.UnqualComponentName
1919

2020
import Distribution.ModuleName
2121

22+
import qualified Distribution.Types.BuildInfo.Lens as L
23+
2224
-- | A \"benchmark\" stanza in a cabal file.
2325
--
2426
data Benchmark = Benchmark {
@@ -28,11 +30,11 @@ data Benchmark = Benchmark {
2830
}
2931
deriving (Generic, Show, Read, Eq, Typeable, Data)
3032

31-
instance HasBuildInfo Benchmark where
32-
buildInfo_ f l = (\x -> l { benchmarkBuildInfo = x }) <$> f (benchmarkBuildInfo l)
33-
3433
instance Binary Benchmark
3534

35+
instance L.HasBuildInfo Benchmark where
36+
buildInfo f (Benchmark x1 x2 x3) = fmap (\y1 -> Benchmark x1 x2 y1) (f x3)
37+
3638
instance Monoid Benchmark where
3739
mempty = Benchmark {
3840
benchmarkName = mempty,

0 commit comments

Comments
 (0)