large-records-0.1.0.0: test/Test/Record/Sanity/Lens/Micro.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-missing-signatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
module Test.Record.Sanity.Lens.Micro (tests) where
import Data.Kind
import Data.Maybe (fromJust)
import Data.SOP
import Lens.Micro (Lens', (^.), (&), (%~))
import Test.Tasty
import Test.Tasty.HUnit
import Data.Record.Generic
import Data.Record.Generic.Lens.Micro
import Data.Record.Generic.SOP
import Data.Record.Generic.Transform
import Data.Record.QQ.CodeGen
import Data.Record.TH
import qualified Data.Record.Generic.Rep as Rep
{-------------------------------------------------------------------------------
Simple example (no type families)
-------------------------------------------------------------------------------}
largeRecord defaultPureScript [d|
data Simple (f :: Type -> Type) = MkSimple {
s1 :: f Int
, s2 :: f Bool
, s3 :: f Char
}
deriving (Show, Eq)
|]
simpleExample :: Simple I
simpleExample = [lr| MkSimple {
s1 = I 5
, s2 = I True
, s3 = I 'a'
} |]
simpleExampleLenses :: Simple (RegularRecordLens Simple f)
simpleExampleLenses = lensesForRegularRecord (Proxy @DefaultInterpretation)
[lr| MkSimple {
s1 = RegularRecordLens xs1
, s2 = RegularRecordLens xs2
, s3 = RegularRecordLens xs3
} |] = simpleExampleLenses
{-------------------------------------------------------------------------------
Simplified version of beam's 'Columnar' type'
-------------------------------------------------------------------------------}
data Lenses (tbl :: (Type -> Type) -> Type) (f :: Type -> Type) (x :: Type)
data WrapLens a b = WrapLens (Lens' a b)
type family Columnar f x :: Type where
Columnar I x = x
Columnar (Lenses tbl f) x = WrapLens (tbl f) (Columnar f x)
Columnar f x = f x
{-------------------------------------------------------------------------------
Example with type families, but still regular
See /next/ example for usage of, and motivation for, 'Lenses'.
-------------------------------------------------------------------------------}
data BeamInterpretation (f :: Type -> Type)
type instance Interpreted (BeamInterpretation f) (Uninterpreted x) = Columnar f x
instance StandardInterpretation BeamInterpretation (RegularRecordLens tbl f)
instance StandardInterpretation BeamInterpretation I
largeRecord defaultPureScript [d|
data Regular (f :: Type -> Type) = MkRegular {
r1 :: Columnar f Int
, r2 :: Columnar f Bool
, r3 :: Columnar f Char
}
deriving (Show, Eq)
|]
regularExample :: Regular I
regularExample = [lr| MkRegular {
r1 = 5
, r2 = True
, r3 = 'a'
} |]
regularLenses :: Regular (RegularRecordLens Regular I)
regularLenses = lensesForRegularRecord (Proxy @BeamInterpretation)
[lr| MkRegular {
r1 = RegularRecordLens xr1
, r2 = RegularRecordLens xr2
, r3 = RegularRecordLens xr3
} |] = regularLenses
{-------------------------------------------------------------------------------
Beam-like example
The lenses we generate above have @I x@ as their argument, rather than @x@. In
beam, the lenses have @Columnar f x@ as their target, which is just @x@ in the
case that @f == I@. If we want to replicate this, we cannot use
'lensesForRegularRecord', which gives us 'RegularRecordLens', and must instead
use the lower-level function 'lensesForHKRecord'. This example is still
simplified from the beam example because we don't support any form of mixins;
we insist every field is regular, which allows us to avoid introducing a
separate type class. See the @beam-large-package@ for full beam integration.
-------------------------------------------------------------------------------}
beamLikeLenses :: forall tbl.
( Generic (tbl (Lenses tbl I))
, Generic (tbl Uninterpreted)
, Generic (tbl I)
, HasNormalForm (BeamInterpretation (Lenses tbl I)) (tbl (Lenses tbl I)) (tbl Uninterpreted)
, HasNormalForm (BeamInterpretation I) (tbl I) (tbl Uninterpreted)
, Constraints (tbl Uninterpreted) (IsRegularField Uninterpreted)
)
=> tbl (Lenses tbl I)
beamLikeLenses =
to . denormalize1 (Proxy @BeamInterpretation) $
Rep.cmap
(Proxy @(IsRegularField Uninterpreted))
aux
(lensesForHKRecord (Proxy @BeamInterpretation))
where
aux :: forall x.
IsRegularField Uninterpreted x
=> HKRecordLens BeamInterpretation I tbl x
-> Interpret (BeamInterpretation (Lenses tbl I)) x
aux (HKRecordLens l) =
case isRegularField (Proxy @(Uninterpreted x)) of
RegularField -> Interpret $ WrapLens $
l
. standardInterpretationLens (Proxy @BeamInterpretation)
. unI'
unI' :: Lens' (I x) x
unI' f (I x) = I <$> f x
regularBeamLikeLenses :: Regular (Lenses Regular I)
regularBeamLikeLenses = beamLikeLenses
[lr| MkRegular {
r1 = WrapLens br1
, r2 = WrapLens br2
, r3 = WrapLens br3
} |] = regularBeamLikeLenses
{-------------------------------------------------------------------------------
Irregular example
-------------------------------------------------------------------------------}
largeRecord defaultPureScript [d|
data Irregular (f :: Type -> Type) = MkIrregular {
i1 :: f Int
, i2 :: f Bool
, i3 :: Char -- No @f@!
}
deriving (Show, Eq)
|]
irregularExample :: Irregular I
irregularExample = [lr| MkIrregular {
i1 = I 5
, i2 = I True
, i3 = 'a'
} |]
-- We cannot define this now:
--
-- > irregularLenses :: Irregular (RegularRecordLens Irregular I)
-- > irregularLenses = lensesForRegularRecord (Proxy @DefaultInterpretation)
--
-- It will complain that @Char@ is not equal to
--
-- > Interpreted (DefaultInterpretation (RegularRecordLens Irregular I)) Char
--
-- We can use 'repLenses' to nonetheless get lenses for all fields in
-- 'Irregular', and then translate to an NP so that we can pattern match on it
-- in a type-safe way. Of course, the translation to SOP incurs O(N^2)
-- compile-time cost so this is not a proper solution.
--
-- NOTE: There is not much point using 'repLenses'' here; that is primarily
-- useful only if there is some post-processing step (like done by
-- 'lensesForRegularRecord').
irregularLenses :: NP (Field (SimpleRecordLens (Irregular f))) (MetadataOf (Irregular f))
irregularLenses = fromJust $ toSOP rep
where
rep :: Rep (SimpleRecordLens (Irregular f)) (Irregular f)
rep = lensesForSimpleRecord
( Field (SimpleRecordLens xi1)
:* Field (SimpleRecordLens xi2)
:* Field (SimpleRecordLens xi3)
:* Nil ) = irregularLenses
{-------------------------------------------------------------------------------
Tests proper
-------------------------------------------------------------------------------}
tests :: TestTree
tests = testGroup "Test.Record.Sanity.Lens.Micro" [
testCase "simple_get" test_simple_get
, testCase "simple_set" test_simple_set
, testCase "regular_get" test_regular_get
, testCase "regular_set" test_regular_set
, testCase "beamlike_get" test_beamlike_get
, testCase "beamlike_set" test_beamlike_set
, testCase "irregular_get" test_irregular_get
, testCase "irregular_set" test_irregular_set
]
test_simple_get :: Assertion
test_simple_get =
assertEqual "" (I True)
(simpleExample ^. xs2)
test_simple_set :: Assertion
test_simple_set =
assertEqual "" expected $
simpleExample & xs1 %~ mapII negate & xs3 %~ mapII succ
where
expected :: Simple I
expected = [lr| MkSimple {
s1 = I (-5)
, s2 = I True
, s3 = I 'b'
} |]
test_regular_get :: Assertion
test_regular_get =
assertEqual "" (I True)
(regularExample ^. xr2)
test_regular_set :: Assertion
test_regular_set =
assertEqual "" expected $
regularExample & xr1 %~ mapII negate & xr3 %~ mapII succ
where
expected :: Regular I
expected = [lr| MkRegular {
r1 = (-5)
, r2 = True
, r3 = 'b'
} |]
test_beamlike_get :: Assertion
test_beamlike_get =
assertEqual "" True
(regularExample ^. br2)
test_beamlike_set :: Assertion
test_beamlike_set =
assertEqual "" expected $
regularExample & br1 %~ negate & br3 %~ succ
where
expected :: Regular I
expected = [lr| MkRegular {
r1 = (-5)
, r2 = True
, r3 = 'b'
} |]
test_irregular_get :: Assertion
test_irregular_get =
assertEqual "" (I True)
(irregularExample ^. xi2)
test_irregular_set :: Assertion
test_irregular_set =
assertEqual "" expected $
irregularExample & xi1 %~ mapII negate & xi3 %~ succ
where
expected :: Irregular I
expected = [lr| MkIrregular {
i1 = I (-5)
, i2 = I True
, i3 = 'b'
} |]