An open API service indexing awesome lists of open source software.

https://github.com/deemp/haskell-barlow-lens

lens via string literals
https://github.com/deemp/haskell-barlow-lens

Last synced: over 1 year ago
JSON representation

lens via string literals

Awesome Lists containing this project

README

          

{-
# barlow-lens

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](https://github.com/sigma-andex/purescript-barlow-lens) based on [generic-lens](https://hackage.haskell.org/package/generic-lens).

## tl;dr
-}

{- FOURMOLU_DISABLE -}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DuplicateRecordFields #-}

{- D -}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use newtype instead of data" #-}

{- E -}
{- FOURMOLU_ENABLE -}

import Control.Lens ((%~), (&), (^.), (^..), (^?))
import Data.Char (toUpper)
import Data.Lens.Barlow
import GHC.Generics

{- D -}

main :: IO ()
main = putStrLn "hello"

{- E -}

{-
## Features

Barlow creates optics for the following types:

- 🥇 [`Records`](#tldr)
- 📦🐈 [`Maybe`](#maybe)
- 🤷🏽‍♀️ [`Either`](#either)
- 📜 [`Traversables`](#traversables)
- 🎁 [`Newtype`](#newtype)
- 🤖 [`Data types`](#data-types)

### Records

`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"

{-
### Maybe

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"

{-
### Either

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 @"zodiacalpha<"

-- >>> starLeftRightLeft
-- Just "Spica"

starLeftLeft :: Maybe VirgoRight
starLeftLeft = skyLeft ^? bw @"zodiac>"

-- >>> starLeftLeft
-- Nothing

{-
### Traversables

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"}]

{-
### Newtype

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"

{-
### Data types

Barlow supports zooming into arbitrary sum and product types as long as there is a `Generic` instance.

Use `%` to zoom into sum types, where `` is the name of your data constructor. E.g. `%VirgoData` for the data constructor `VirgoData`.

Use `%` to zoom into product types, where `` 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.

- `%` ~ `_Ctor :: AsConstructor ctor s t a b => Prism s t a b`
- `%` ~ `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"

{-

## Prerequisites

Spoiler

- [flake.nix](./flake.nix) - code in this flake is extensively commented.
- [codium-haskell](https://github.com/deemp/flakes/tree/main/templates/codium/haskell#readme) - this flake.
- [codium-haskell-simple](https://github.com/deemp/flakes/tree/main/templates/codium/haskell-simple#readme) - a simplified version of this flake.
- [language-tools/haskell](https://github.com/deemp/flakes/blob/main/language-tools/haskell/flake.nix) - a flake that conveniently provides `Haskell` tools.
- [Conventions](https://github.com/deemp/flakes/blob/main/README/Conventions.md#dev-tools)
- [codium-generic](https://github.com/deemp/flakes/tree/main/templates/codium/generic#readme) - info just about `VSCodium` with extensions.
- [Haskell](https://github.com/deemp/flakes/blob/main/README/Haskell.md) - general info about `Haskell` tools.
- [Troubleshooting](https://github.com/deemp/flakes/blob/main/README/Troubleshooting.md)
- [Prerequisites](https://github.com/deemp/flakes#prerequisites)
- [Nixpkgs support for incremental Haskell builds](https://www.haskellforall.com/2022/12/nixpkgs-support-for-incremental-haskell.html)
- [flakes](https://github.com/deemp/flakes#readme) - my Nix flakes that may be useful for you.

## Quick start

1. Install Nix - see [how](https://github.com/deemp/flakes/blob/main/README/InstallNix.md).

1. In a new terminal, start a devshell, build and test the app.

```console
nix develop
cabal build
cabal test
```

1. Write `settings.json` and start `VSCodium`.

```console
nix run .#writeSettings
nix run .#codium .
```

1. Open a `Haskell` file `app/Main.hs` and hover over a function.

1. Wait until `Haskell Language Server` (`HLS`) starts giving you type info.

1. Sometimes, `cabal` doesn't use the `Nix`-supplied packages ([issue](https://github.com/NixOS/nixpkgs/issues/130556#issuecomment-1114239002)). In this case, use `cabal v1-*` - commands.

## Configs

- [package.yaml](./package.yaml) - used by `stack` or `hpack` to generate a `.cabal`
- [.markdownlint.jsonc](./.markdownlint.jsonc) - for `markdownlint` from the extension `davidanson.vscode-markdownlint`
- [.ghcid](./.ghcid) - for [ghcid](https://github.com/ndmitchell/ghcid)
- [.envrc](./.envrc) - for [direnv](https://github.com/direnv/direnv)
- [fourmolu.yaml](./fourmolu.yaml) - for [fourmolu](https://github.com/fourmolu/fourmolu#configuration)
- [ci.yaml](.github/workflows/ci.yaml) - a generated `GitHub Actions` workflow. See [workflows](https://github.com/deemp/flakes/tree/main/workflows). Generate a workflow via `nix run .#writeWorkflows`.
- [hie.yaml](./hie.yaml) - a config for [hie-bios](https://github.com/haskell/hie-bios). Can be generated via [implicit-hie](https://github.com/Avi-D-coder/implicit-hie) to check the `Haskell Language Server` setup.
-}