miso
Copyright(C) 2016-2026 David M. Johnson
LicenseBSD3-style (see the file LICENSE)
MaintainerDavid M. Johnson <code@dmj.io>
Stabilityexperimental
Portabilitynon-portable
Safe HaskellNone
LanguageHaskell2010

Miso.Lens.Generic

Description

Overview

Miso.Lens.Generic derives Lens values for record fields at compile time using GHC.Generics and GHC.Records, without Template Haskell. Fields are addressed by name via OverloadedLabels or the explicit field combinator.

Enable the required extensions:

{-# LANGUAGE OverloadedLabels, DeriveGeneric #-}
import GHC.Generics (Generic)
import Miso.Lens.Generic (HasLens, field)
import Miso.Lens         (Lens, view, set, (.=), (++=))

Quick start

data Counter = Counter { _count :: Int, _label :: MisoString }
  deriving (Generic)

-- Label syntax (requires OverloadedLabels):
countLens :: Lens Counter Int
countLens = #_count

-- Explicit syntax (works without OverloadedLabels):
labelLens :: Lens Counter MisoString
labelLens = field @"_label"

update :: Action -> Effect p props Counter Action
update Increment = #_count += 1
update (SetLabel l) = #_label .= l

How it works

The HasLens instance is resolved via HasField for the getter and a generic traversal (GSet) for the setter. A type-level TotalityCheck produces a descriptive compile error if the field name is absent from (or inconsistent across) the constructors.

Comparison with Template Haskell

Overloaded labels
Miso.Lens.Generic — no TH; derives via Generic
Template Haskell
Miso.Lens.THmakeLenses / makeClassy

See also

Synopsis

Documentation

class Generic s => HasLens (name :: Symbol) s a | name s -> a where Source #

Provides a Lens onto the field named name of the record s.

The single instance is derived from s's Generic representation, so any record with a Generic instance gets lenses for free. Also backs the OverloadedLabels syntax #fieldName.

Since: 1.11.0.0

Methods

getLens :: Lens s a Source #

Instances

Instances details
(HasField name s a, TotalityCheck name s a (GetFieldType name (Rep s)), GSet name a (Rep s), Generic s) => HasLens name s a Source # 
Instance details

Defined in Miso.Lens.Generic

Methods

getLens :: Lens s a Source #

field :: forall (name :: Symbol) s a. HasLens name s a => Lens s a Source #

A Lens onto the record field named name, applied with a type application: field @"userName".

The OverloadedLabels form #userName is equivalent.

Since: 1.11.0.0

class GSet (name :: Symbol) typ (f :: Type -> Type) where Source #

Internal: writes the field named name into a Generic representation. Drives the setter half of HasLens; you should not need to write instances.

Since: 1.13.0.0

Methods

gSet :: typ -> f x -> f x Source #

Instances

Instances details
(GSet name typ a, GSet name typ b) => GSet name typ (a :*: b) Source # 
Instance details

Defined in Miso.Lens.Generic

Methods

gSet :: typ -> (a :*: b) x -> (a :*: b) x Source #

(GSet name typ a, GSet name typ b) => GSet name typ (a :+: b) Source # 
Instance details

Defined in Miso.Lens.Generic

Methods

gSet :: typ -> (a :+: b) x -> (a :+: b) x Source #

GSet name typ f => GSet name typ (C1 x f) Source # 
Instance details

Defined in Miso.Lens.Generic

Methods

gSet :: typ -> C1 x f x0 -> C1 x f x0 Source #

GSet name typ f => GSet name typ (D1 x f) Source # 
Instance details

Defined in Miso.Lens.Generic

Methods

gSet :: typ -> D1 x f x0 -> D1 x f x0 Source #

GSet name typ (S1 ('MetaSel ('Just anotherName) b c d) x) Source # 
Instance details

Defined in Miso.Lens.Generic

Methods

gSet :: typ -> S1 ('MetaSel ('Just anotherName) b c d) x x0 -> S1 ('MetaSel ('Just anotherName) b c d) x x0 Source #

GSet name typ (S1 ('MetaSel ('Just name) b c d) (Rec0 typ)) Source # 
Instance details

Defined in Miso.Lens.Generic

Methods

gSet :: typ -> S1 ('MetaSel ('Just name) b c d) (Rec0 typ) x -> S1 ('MetaSel ('Just name) b c d) (Rec0 typ) x Source #

type family GetFieldType (field :: Symbol) (f :: Type -> Type) :: Maybe Type where ... Source #

Internal: looks up the type of the field named field in a Generic representation, yielding Just t when found. Products search both sides (Or); sums require agreement (And).

Since: 1.13.0.0

Equations

GetFieldType field (S1 ('MetaSel ('Just field) _1 _2 _3) (Rec0 t)) = 'Just t 
GetFieldType field (l :*: r) = Or (GetFieldType field l) (GetFieldType field r) 
GetFieldType field (l :+: r) = And (GetFieldType field l) (GetFieldType field r) 
GetFieldType field (C1 _1 f) = GetFieldType field f 
GetFieldType field (D1 _1 f) = GetFieldType field f 
GetFieldType field x = 'Nothing :: Maybe Type 

type family TotalityCheck (name :: Symbol) r a (res :: Maybe Type) where ... Source #

Internal: turns a missing field into a readable TypeError rather than an unsolved-constraint message. Fails when GetFieldType found no field of that name, or found one that is not present in every constructor.

Since: 1.13.0.0

Equations

TotalityCheck _1 _2 _3 ('Just _4) = () 
TotalityCheck name r a ('Nothing :: Maybe Type) = TypeError ((('ShowType r ':<>: 'Text ": ") ':<>: 'Text name) ':<>: 'Text " field missing or not in all constructors") :: Constraint 

type family And (l :: Maybe Type) (r :: Maybe Type) :: Maybe Type where ... Source #

Internal: combines two GetFieldType results across a sum, yielding Just t only when both branches agree on t — a field that is absent from some constructor is not addressable.

Since: 1.13.0.0

Equations

And ('Just a) ('Just a) = 'Just a 
And l r = 'Nothing :: Maybe Type 

type family Or (l :: Maybe Type) (r :: Maybe Type) :: Maybe Type where ... Source #

Internal: combines two GetFieldType results across a product, taking whichever branch found the field.

Since: 1.13.0.0

Equations

Or ('Just l) _1 = 'Just l 
Or _1 r = r 

Orphan instances

HasLens name s a => IsLabel name (Lens s a) Source # 
Instance details

Methods

fromLabel :: Lens s a #