The generic-lens package

[ Tags: bsd3, generics, lens, library, records ] [ Propose Tags ]

This package uses the GHC 8 Generic representation to derive various operations on data structures with a lens interface, including structural subtype relationship between records and positional indexing into arbitrary product types.


[Skip to Readme]

Properties

Versions 0.1.0.0, 0.2.0.0, 0.3.0.0, 0.3.0.1, 0.4.0.0, 0.4.0.1
Change log ChangeLog.md
Dependencies base (>=4.9 && <=5.0), profunctors (>=5.0 && <=6.0) [details]
License BSD3
Author Csongor Kiss
Maintainer kiss.csongor.kiss@gmail.com
Category Generics, Records, Lens
Home page https://github.com/kcsongor/generic-lens
Source repository head: git clone https://github.com/kcsongor/generic-lens
Uploaded Wed Sep 6 13:51:51 UTC 2017 by kcsongor
Distributions NixOS:0.4.0.1
Downloads 225 total (64 in the last 30 days)
Rating 0.0 (0 ratings) [clear rating]
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]
Hackage Matrix CI

Modules

[Index]

Downloads

Maintainer's Corner

For package maintainers and hackage trustees


Readme for generic-lens-0.4.0.1

[back to package description]

generic-lens

Build
Status

Generically derive lenses and prisms for data types.

Available on Hackage

This package uses the GHC 8 Generic representation to derive various operations on data structures with lens interfaces, including structural subtype relationships between records and positional indexing into arbitrary product types.

This is made possible by GHC 8's new Generics API, which provides metadata at the type-level (previously only value-level metadata was available).

Examples can be found in the examples folder. This library makes heavy use of Visible Type Applications.

Lenses

Record fields

Record fields can be accessed by their label:

data Person = Person { name :: String, age :: Int } deriving (Generic, Show)

sally :: Person
sally = Person "Sally" 25
>>> getField @"age" sally
25

>>> setField @"age" 26 sally
Person {name = "Sally", age = 26}

>>> sally ^. field @"name"
"Sally"

>>> sally & field @"name" .~ "Tamas"
Person {name = "Tamas", age = 25}

>>> sally ^. field @"pet"
error:
  • The type Person does not contain a field named "pet"

Positional fields

Fields can be accessed by their position in the data structure (index starting at 1):

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))
>>> getPosition @2 polygon
Point 2 4 2

>>> setPosition @1 (Point 26 5 3) polygon
Polygon (Point 26 5 3) (Point 2 4 2) (Point 5 7 (-2))

>>> 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, they also have positional lenses:

>>> (("hello", True), 5) ^. position @1 . position @2
True

Typed fields

Fields can be accessed by their type in the data structure, assuming that this type is unique:

data Person = Person { name :: String, age :: Int } deriving (Generic, Show)
data Point = Point Int Int Int deriving (Generic, Show)

sally :: Person
sally = Person "Sally" 25

point :: Point
point = Point 1 2 3
>>> getTyped @String sally
"Sally"

>>> setTyped @Int sally 26
Person {name = "Sally", age = 26}

>>> point ^. typed @Int
error:
  • The type Point contains multiple values of type Int; the choice of value is thus ambiguous

>>> point & typed @String .~ "Point"
error:
  • The type Point does not contain a value of type [Char]

Structural subtyping

A record is a (structural) `subtype' of another, if its fields are a superset of those of the other.

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

>>> upcast human :: Animal
Animal {name = "Tunyasz", age = 50}

-- 'smash' plug the smaller structure into the larger one
>>> smash (Animal "dog" 10) human
Human {name = "dog", age = 10, address = "London"}

-- 'super' is a lens that focuses on a subrecord of a larger record:
>>> human ^. super @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:

growUp :: Animal -> Animal
growUp (Animal name age) = Animal name (age + 50)

>>> human & super @Animal %~ growUp
Human {name = "Tunyasz", age = 60, address = "London"}

Prisms

Named constructors

Constructor components can be accessed using the constructor's name:

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
>>> shep ^? _Ctor @"Dog"
Just (MkDog {name = "Shep", age = 4})

>>> shep ^? _Ctor @"Cat"
Nothing

>>> mog ^? _Ctor @"Cat"
Just ("Mog",5)

>>> _Ctor @"Cat" # ("Garfield", 6) :: Animal
Cat "Garfield" 6

>>> donald ^? _Ctor @"Giraffe"
error:
  • The type Animal does not contain a constructor named "Giraffe"

Typed constructors

Constructor components can be accessed using the component's type, assuming that this type is unique:

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
>>> mog ^? _Typed @Dog
Nothing

>>> shep ^? _Typed @Dog
Just (MkDog {name = "Shep", age = 4})

>>> donald ^? _Typed @Age
Just 4

>>> donald ^? _Typed @Float
error:
  • The type Animal does not contain a constructor whose field is of type Float

>>> _Typed @Age # 6 :: Animal
Duck 6

Contributors