Barlow lens increases your magnification and lets you see star sparkles.
In other words, barlow-lens
simplifies creating complex lenses such as record lenses.
This package is a port of purescript-barlow-lens based on generic-lens.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DuplicateRecordFields #-}
import Control.Lens ((%~), (&), (^.), (^..), (^?))
import Data.Char (toUpper)
import Data.Lens.Barlow
import GHC.Generics
Barlow creates optics for the following types:
- π₯
Records
- π¦π
Maybe
- π€·π½ββοΈ
Either
- π
Traversables
- π
Newtype
- π€
Data types
zodiac
~ field @"zodiac"
data AlphaRecord = AlphaRecord {alpha :: String} deriving (Generic, Show)
data VirgoRecord = VirgoRecord {virgo :: AlphaRecord} deriving (Generic, Show)
data ZodiacRecord = ZodiacRecord {zodiac :: VirgoRecord} deriving (Generic, Show)
sky :: ZodiacRecord
sky = ZodiacRecord{zodiac = VirgoRecord{virgo = AlphaRecord{alpha = "Spica"}}}
spica :: String
spica = sky ^. (bw @"zodiac.virgo.alpha")
-- >>> spica
-- "Spica"
-- >>> alfa = sky ^. barlow @"zodiac.virgo.alfa"
-- The type AlphaRecord does not contain a field named 'alfa'.
-- In the second argument of `(^.)', namely
-- `barlow @"zodiac.virgo.alfa"'
-- In the expression: sky ^. barlow @"zodiac.virgo.alfa"
-- In an equation for `alfa':
-- alfa = sky ^. barlow @"zodiac.virgo.alfa"
Use ?
to zoom into a Maybe
.
?
~_Just :: Prism (Maybe a) (Maybe b) a b
newtype AlphaMaybe = AlphaMaybe {alpha :: Maybe String} deriving (Generic, Show)
newtype VirgoMaybe = VirgoMaybe {virgo :: Maybe AlphaMaybe} deriving (Generic, Show)
newtype ZodiacMaybe = ZodiacMaybe {zodiac :: Maybe VirgoMaybe} deriving (Generic, Show)
skyMaybe :: ZodiacMaybe
skyMaybe = ZodiacMaybe{zodiac = Just VirgoMaybe{virgo = Just AlphaMaybe{alpha = Just "Spica"}}}
spicaMaybe :: Maybe String
spicaMaybe = skyMaybe ^? bw @"zodiac?.virgo?.alpha?"
-- >>> spicaMaybe
-- Just "Spica"
Use <
for Left
and >
for Right
to zoom into an Either
.
<
~_Left :: Prism (Either a c) (Either b c) a b
>
~_Right :: Prism (Either c a) (Either c b) a b
newtype AlphaLeft = AlphaLeft {alpha :: Either String ()} deriving (Generic, Show)
newtype VirgoRight = VirgoRight {virgo :: Either () AlphaLeft} deriving (Generic, Show)
newtype ZodiacEither = ZodiacEither {zodiac :: Either VirgoRight VirgoRight} deriving (Generic, Show)
skyLeft :: ZodiacEither
skyLeft = ZodiacEither{zodiac = Left VirgoRight{virgo = Right AlphaLeft{alpha = Left "Spica"}}}
starLeftRightLeft :: Maybe String
starLeftRightLeft = skyLeft ^? bw @"zodiac<virgo>alpha<"
-- >>> starLeftRightLeft
-- Just "Spica"
starLeftLeft :: Maybe VirgoRight
starLeftLeft = skyLeft ^? bw @"zodiac>"
-- >>> starLeftLeft
-- Nothing
Use +
to zoom into Traversable
s.
+
~traversed :: Traversable f => IndexedTraversal Int (f a) (f b) a b
newtype AlphaLeftRight = AlphaLeftRight {alpha :: Either String String} deriving (Generic, Show)
newtype VirgoLeftRight = VirgoLeftRight {virgo :: Either AlphaLeftRight AlphaLeftRight} deriving (Generic, Show)
newtype ZodiacList = ZodiacList {zodiac :: [VirgoLeftRight]} deriving (Generic, Show)
skyList :: ZodiacList
skyList =
ZodiacList
{ zodiac =
[ VirgoLeftRight{virgo = Right AlphaLeftRight{alpha = Left "Spica1"}}
, VirgoLeftRight{virgo = Right AlphaLeftRight{alpha = Right "Spica2"}}
, VirgoLeftRight{virgo = Left AlphaLeftRight{alpha = Right "Spica3"}}
, VirgoLeftRight{virgo = Left AlphaLeftRight{alpha = Left "Spica4"}}
]
}
starList :: [String]
starList = skyList ^.. bw @"zodiac+virgo>alpha>" & bw @"++" %~ toUpper
-- >>> starList
-- ["SPICA2"]
alphaRight :: [AlphaLeftRight]
alphaRight = skyList ^.. bw @"zodiac+virgo>"
-- >>> alphaRight
-- [AlphaLeftRight {alpha = Left "Spica1"},AlphaLeftRight {alpha = Right "Spica2"}]
Use !
to zoom into a newtype
.
!
~wrappedIso :: Iso s t a b
newtype AlphaNewtype = AlphaNewtype {alpha :: String} deriving (Generic)
newtype VirgoNewtype = VirgoNewtype {virgo :: AlphaNewtype} deriving (Generic)
newtype ZodiacNewtype = ZodiacNewtype {zodiac :: VirgoNewtype} deriving (Generic)
skyNewtype :: ZodiacNewtype
skyNewtype = ZodiacNewtype (VirgoNewtype (AlphaNewtype "Spica"))
starNewtype :: [Char]
starNewtype = skyNewtype ^. bw @"zodiac!!"
-- >>> starNewtype
-- "Spica"
Barlow supports zooming into arbitrary sum and product types as long as there is a Generic
instance.
Use %<NAME>
to zoom into sum types, where <NAME>
is the name of your data constructor. E.g. %VirgoData
for the data constructor VirgoData
.
Use %<INDEX>
to zoom into product types, where <INDEX>
is a natural number.
Note that counting for product types and tuples usually starts with 1 and not 0.
So the first element of a product is %1
.
It is more readable if you separate your sum lens from your product lens with a .
dot.
%<NAME>
~_Ctor :: AsConstructor ctor s t a b => Prism s t a b
%<INDEX>
~position :: HasPosition i s t a b => Lens s t a b
data ZodiacData
= CarinaData {alpha :: String}
| VirgoData {alpha :: String, beta :: String, gamma :: String, delta :: String}
| CanisMaiorData String
deriving (Generic)
skyData :: ZodiacData
skyData = VirgoData{alpha = "Spica", beta = "Beta Vir", gamma = "Gamma Vir", delta = "Del Vir"}
starData :: [Char]
starData = skyData ^. bw @"%VirgoData%3"
-- >>> starData
-- "Gamma Vir"
Spoiler
- flake.nix - code in this flake is extensively commented.
- codium-haskell - this flake.
- codium-haskell-simple - a simplified version of this flake.
- language-tools/haskell - a flake that conveniently provides
Haskell
tools. - Conventions
- codium-generic - info just about
VSCodium
with extensions. - Haskell - general info about
Haskell
tools. - Troubleshooting
- Prerequisites
- Nixpkgs support for incremental Haskell builds
- flakes - my Nix flakes that may be useful for you.
-
Install Nix - see how.
-
In a new terminal, start a devshell, build and test the app.
nix develop cabal build cabal test
-
Write
settings.json
and startVSCodium
.nix run .#writeSettings nix run .#codium .
-
Open a
Haskell
fileapp/Main.hs
and hover over a function. -
Wait until
Haskell Language Server
(HLS
) starts giving you type info. -
Sometimes,
cabal
doesn't use theNix
-supplied packages (issue). In this case, usecabal v1-*
- commands.
- package.yaml - used by
stack
orhpack
to generate a.cabal
- .markdownlint.jsonc - for
markdownlint
from the extensiondavidanson.vscode-markdownlint
- .ghcid - for ghcid
- .envrc - for direnv
- fourmolu.yaml - for fourmolu
- ci.yaml - a generated
GitHub Actions
workflow. See workflows. Generate a workflow vianix run .#writeWorkflows
. - hie.yaml - a config for hie-bios. Can be generated via implicit-hie to check the
Haskell Language Server
setup.