on 433713f help sign in
14 files changed+101−198
since splitsnapshots
unifiedsplit
.github/workflows/ci.yaml+13−7 .vscode/extensions.json−11 .vscode/settings.json+4−5 CHANGELOG.md−4 Setup.hs+2 bin/test+1−1 default.nix+36 flake.lock−81 flake.nix−34 jrec.cabal+3−15 shell.nix−1 src/JRec.hs+7−4 src/JRec/Internal.hs+31−25 test/JRecSpec.hs+4−10
.github/workflows/ci.yaml+13−7
@@ -4,14 +4,20 @@ on:
44 push:
55jobs:
66 build:
7− runs-on: ubuntu-latest
7+ runs-on: ${{ matrix.os }}
8+ strategy:
9+ matrix:
10+ ghc: ['ghc884', 'ghc8101']
11+ os: [ubuntu-latest]
12+ name: GHC ${{ matrix.ghc }}
813 steps:
914 - uses: actions/checkout@v2
10− - uses: nixbuild/nix-quick-install-action@v21
15+ - uses: cachix/install-nix-action@v10
16+ - uses: cachix/cachix-action@v6
1117 with:
12− nix_conf: |
13− experimental-features = nix-command flakes
18+ name: juspay-nau
19+ signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
20+ # Only needed for private caches
21+ authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
1422 - name: Build and test 🔧
15− run: nix build -j 4 -L
16− - name: Build and test (GHC 8.8) 🔧
17− run: nix build -j 4 -L .#jrec-ghc88
23+ run: nix-build -j4 --argstr compiler ${{ matrix.ghc }}
.vscode/extensions.jsondeleted−11
@@ -1,11 +0,0 @@
1−{
2− // See https://go.microsoft.com/fwlink/?LinkId=827846
3− // for the documentation about the extensions.json format
4− "recommendations": [
5− "haskell.haskell",
6− "arrterian.nix-env-selector",
7− "bbenoist.nix",
8− "jnoortheen.nix-ide",
9− "tamasfe.even-better-toml"
10− ]
11−}
.vscode/settings.json+4−5
@@ -1,6 +1,5 @@
11{
2− "editor.formatOnType": true,
3− "editor.formatOnSave": true,
4− "nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix",
5− "haskell.manageHLS": "PATH"
6−}
2+ "nixEnvSelector.nixShellConfig": "${workspaceRoot}/default.nix",
3+ "editor.formatOnType": true,
4+ "editor.formatOnSave": true
5+}
CHANGELOG.md−4
@@ -1,9 +1,5 @@
11# Revision history for rec
22
3−## Unreleased
4−
5−- GHC 9.2 + Aeson 2.0 support
6−
73## 0.1.0.0 -- YYYY-mm-dd
84
95* First version. Released on an unsuspecting world.
Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
bin/test+1−1
@@ -2,4 +2,4 @@
22set -xe
33
44# NOTE: ghcid should be run with -W to allow warnings from should-not-typecheck
5−nix develop -c sh -c "ghcid $* -W -c 'cabal new-repl test:jrec-test' -T \":main $*\""
5+nix-shell --pure --run "ghcid $* -W -c 'cabal new-repl test:jrec-test' -T \":main $*\""
default.nixnew file+36
@@ -0,0 +1,36 @@
1+let
2+ nixpkgsSrc = builtins.fetchTarball {
3+ url = "https://github.com/nixos/nixpkgs/archive/76f2e271a2ef.tar.gz";
4+ sha256 = "1r4da3kyghqq7vzya5yb295jpnha1fgp755bi6wda4fmhyaynnsp";
5+ };
6+ gitignoreSrc = builtins.fetchTarball {
7+ url = "https://github.com/hercules-ci/gitignore/archive/c4662e6.tar.gz";
8+ sha256 = "1npnx0h6bd0d7ql93ka7azhj40zgjp815fw2r6smg8ch9p7mzdlx";
9+ };
10+ inherit (import gitignoreSrc { inherit (pkgs); }) gitignoreSource;
11+in {
12+ compiler ? "default",
13+ pkgs ? import nixpkgsSrc {},
14+}:
15+ let
16+ haskellPackages =
17+ if compiler == "default"
18+ then pkgs.haskellPackages
19+ else pkgs.haskell.packages.${compiler};
20+ projectDrv =
21+ haskellPackages.override {
22+ overrides = self: super: with pkgs.haskell.lib; {
23+ jrec = self.callCabal2nix "jrec" (gitignoreSource ./.) {};
24+ # https://github.com/lspitzner/data-tree-print/pull/2
25+ data-tree-print = doJailbreak super.data-tree-print;
26+ };
27+ };
28+ projectShell =
29+ projectDrv.shellFor {
30+ packages = p: [ p.jrec ];
31+ buildInputs = with projectDrv; [
32+ cabal-install
33+ ];
34+ };
35+ in
36+ if pkgs.lib.inNixShell then projectShell else projectDrv.jrec
flake.lockdeleted−81
@@ -1,81 +0,0 @@
1−{
2− "nodes": {
3− "flake-parts": {
4− "inputs": {
5− "nixpkgs-lib": "nixpkgs-lib"
6− },
7− "locked": {
8− "lastModified": 1678379998,
9− "narHash": "sha256-TZdfNqftHhDuIFwBcN9MUThx5sQXCTeZk9je5byPKRw=",
10− "owner": "hercules-ci",
11− "repo": "flake-parts",
12− "rev": "c13d60b89adea3dc20704c045ec4d50dd964d447",
13− "type": "github"
14− },
15− "original": {
16− "owner": "hercules-ci",
17− "repo": "flake-parts",
18− "type": "github"
19− }
20− },
21− "nixpkgs": {
22− "locked": {
23− "lastModified": 1678594102,
24− "narHash": "sha256-OHAHYiMWJFPNxuW/PcOMlSD2tvXnEYC1jxREBADHwwQ=",
25− "owner": "nixos",
26− "repo": "nixpkgs",
27− "rev": "796b4a3c1d903c4b9270cd2548fe46f524eeb886",
28− "type": "github"
29− },
30− "original": {
31− "owner": "nixos",
32− "ref": "nixpkgs-unstable",
33− "repo": "nixpkgs",
34− "type": "github"
35− }
36− },
37− "nixpkgs-ghc88": {
38− "locked": {
39− "lastModified": 1596466290,
40− "narHash": "sha256-V1vrlYfVEdW4iauUc58LCtorSxLLF+X/PhjD5+dQjeQ=",
41− "owner": "nixos",
42− "repo": "nixpkgs",
43− "rev": "76f2e271a2ef9de3734dcc9d366a42d6bfb18e82",
44− "type": "github"
45− },
46− "original": {
47− "owner": "nixos",
48− "ref": "76f2e271a2ef",
49− "repo": "nixpkgs",
50− "type": "github"
51− }
52− },
53− "nixpkgs-lib": {
54− "locked": {
55− "dir": "lib",
56− "lastModified": 1678375444,
57− "narHash": "sha256-XIgHfGvjFvZQ8hrkfocanCDxMefc/77rXeHvYdzBMc8=",
58− "owner": "NixOS",
59− "repo": "nixpkgs",
60− "rev": "130fa0baaa2b93ec45523fdcde942f6844ee9f6e",
61− "type": "github"
62− },
63− "original": {
64− "dir": "lib",
65− "owner": "NixOS",
66− "ref": "nixos-unstable",
67− "repo": "nixpkgs",
68− "type": "github"
69− }
70− },
71− "root": {
72− "inputs": {
73− "flake-parts": "flake-parts",
74− "nixpkgs": "nixpkgs",
75− "nixpkgs-ghc88": "nixpkgs-ghc88"
76− }
77− }
78− },
79− "root": "root",
80− "version": 7
81−}
flake.nixdeleted−34
@@ -1,34 +0,0 @@
1−{
2− inputs = {
3− nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
4− nixpkgs-ghc88.url = "github:nixos/nixpkgs/76f2e271a2ef";
5− flake-parts.url = "github:hercules-ci/flake-parts";
6− };
7− outputs = inputs@{ nixpkgs, flake-parts, ... }:
8− flake-parts.lib.mkFlake { inherit inputs; } {
9− systems = nixpkgs.lib.systems.flakeExposed;
10− perSystem = { self', inputs', pkgs, system, ... }:
11− let
12− overlay = self: super: {
13− jrec = self.callCabal2nix "jrec" ./. { };
14− };
15− ghcVersions = {
16− ghc88 = inputs.nixpkgs-ghc88.legacyPackages.${system}.haskellPackages.extend overlay;
17− ghc92 = pkgs.haskellPackages.extend overlay;
18− };
19− in
20− {
21− packages.default = ghcVersions.ghc92.jrec;
22− devShells.default = ghcVersions.ghc92.shellFor {
23− packages = p: [ p.jrec ];
24− buildInputs = with ghcVersions.ghc92; [
25− cabal-install
26− ghcid
27− haskell-language-server
28− ];
29− };
30− # Expose jrec built with GHC 8.8 so it can be tested in CI.
31− packages.jrec-ghc88 = ghcVersions.ghc88.jrec;
32− };
33− };
34−}
jrec.cabal+3−15
@@ -1,6 +1,6 @@
11cabal-version: 2.4
22name: jrec
3−version: 0.1.0.1
3+version: 0.1.0.0
44synopsis: anonymous records for busy people
55
66-- description:
@@ -24,20 +24,13 @@ flag with-aeson
2424 -- with instances by default
2525 default: True
2626
27−flag with-generics
28− description: Enable GHC.Generics instances
29− -- don't let the solver fiddle with the flag
30− manual: True
31− -- with instances by default
32− default: True
33−
3427-- A common stanza to share with tests, so that ghcid (bin/test) will reload
3528-- instantly when the library sources change (without us having to restart it).
3629common library-common
3730 default-language: Haskell2010
3831 build-depends:
3932 aeson
40− , base >=4.13 && <4.17
33+ , base >=4.13 && <4.15
4134 , constraints
4235 , deepseq
4336 , generic-data
@@ -88,18 +81,13 @@ library
8881 JRec.Field
8982 if flag(with-aeson)
9083 cpp-options: -DWITH_AESON
91− if flag(with-generics)
92− cpp-options: -DWITH_GENERICS
9384
9485test-suite jrec-test
9586 import: library-common
9687 type: exitcode-stdio-1.0
9788 hs-source-dirs: test, src
9889 main-is: Spec.hs
99− cpp-options: -DWITH_AESON -DWITH_GENERICS
100− other-modules:
101− JRecShouldNotTypecheckSpec
102− JRecSpec
90+ cpp-options: -DWITH_AESON
10391 build-depends:
10492 base,
10593 hspec,
shell.nixdeleted−1
@@ -1 +0,0 @@
1−(builtins.getFlake ("git+file://" + toString ./.)).devShells.${builtins.currentSystem}.default
src/JRec.hs+7−4
@@ -1,4 +1,3 @@
1−{-# LANGUAGE CPP #-}
21{-# LANGUAGE GADTs #-}
32{-# LANGUAGE PatternSynonyms #-}
43{-# LANGUAGE TypeApplications #-}
@@ -50,7 +49,13 @@ unField _ (_ R.:= value) = value
5049-- FIXME: See spec for a bug when there are duplicates.
5150append ::
5251 forall lhs rhs res.
53− res ~ R.RecAppend lhs rhs =>
52+ ( KnownNat (R.RecSize lhs),
53+ KnownNat (R.RecSize rhs),
54+ KnownNat (R.RecSize lhs + R.RecSize rhs),
55+ res ~ R.RecAppend lhs rhs,
56+ R.RecCopy lhs lhs res,
57+ R.RecCopy rhs rhs res
58+ ) =>
5459 Rec lhs ->
5560 Rec rhs ->
5661 Rec res
@@ -129,7 +134,6 @@ type RecordRep fields =
129134 (Sels fields)
130135 )
131136
132−#ifdef WITH_GENERICS
133137instance
134138 (R.FromNative (RecordRep fields) fields, R.ToNative (RecordRep fields) fields) =>
135139 Generic (Rec fields)
@@ -139,7 +143,6 @@ instance
139143 RecordRep fields
140144 from r = R.toNative' r
141145 to rep = R.fromNative' rep
142−#endif
143146
144147----------------------------------------------------------------------------
145148-- generic-lens
src/JRec/Internal.hs+31−25
@@ -32,7 +32,6 @@ import qualified Control.Monad.State as S
3232import Data.Aeson
3333import Data.Aeson.Types (Parser)
3434import Data.Constraint
35−import Data.List (intercalate)
3635import Data.Maybe (catMaybes)
3736import Data.Proxy
3837import qualified Data.Text as T
@@ -43,17 +42,11 @@ import GHC.OverloadedLabels
4342import GHC.Prim
4443import GHC.ST (ST (..), runST)
4544import GHC.TypeLits
45+import Generic.Data (gshowsPrec)
46+import Generic.Data.Internal.Show (GShow)
4647import qualified Optics.Lens as OL
4748import Unsafe.Coerce
4849import Prelude
49−#if MIN_VERSION_aeson(2,0,0)
50−import Data.Aeson.Key (fromText, fromString)
51−#else
52−fromText :: a -> a
53−fromText = id
54−fromString :: String -> T.Text
55−fromString = T.pack
56−#endif
5750
5851-- | Field named @l@ labels value of type @t@ adapted from the awesome /labels/ package.
5952-- Example: @(#name := \"Chris\") :: (\"name\" := String)@
@@ -109,12 +102,13 @@ defaultJSONOptions = JSONOptions {fieldTransform = id}
109102type role Rec representational
110103
111104instance
112− ( RecApply lts lts Show
105+ ( RecApply lts lts Show,
106+ GShow Proxy (Rep (Rec lts)),
107+ Generic (Rec lts)
113108 ) =>
114109 Show (Rec lts)
115110 where
116− show rec = "{" ++ formatted ++ "}"
117− where formatted = intercalate ", " $ map (\(k, v) -> k ++ " = " ++ v) $ showRec rec
111+ show = (\x -> if null x then "{}" else x) . unwords . drop 1 . words . flip (gshowsPrec 0) ""
118112
119113instance RecEq lts lts => Eq (Rec lts) where
120114 (==) (a :: Rec lts) (b :: Rec lts) = recEq a b (Proxy :: Proxy lts)
@@ -409,20 +403,26 @@ type family RecDeepTy (ps :: r) (lts :: [*]) :: * where
409403-- NOTE: changed from original superrecord to not require a 'Sort'
410404combine ::
411405 forall lhs rhs res.
412− res ~ RecAppend lhs rhs =>
406+ ( KnownNat (RecSize lhs),
407+ KnownNat (RecSize rhs),
408+ KnownNat (RecSize lhs + RecSize rhs),
409+ res ~ RecAppend lhs rhs,
410+ RecCopy lhs lhs res,
411+ RecCopy rhs rhs res
412+ ) =>
413413 Rec lhs ->
414414 Rec rhs ->
415415 Rec res
416−combine (MkRec arr0#) (MkRec arr1#) =
417− let !size0# = sizeofSmallArray# arr0#
418− !size1# = sizeofSmallArray# arr1#
416+combine lts rts =
417+ let !(I# size#) =
418+ fromIntegral $ natVal' (proxy# :: Proxy# (RecSize lhs + RecSize rhs))
419419 in runST' $
420420 ST $ \s# ->
421− case newSmallArray# (size0# +# size1#) (error "No value") s# of
421+ case newSmallArray# size# (error "No value") s# of
422422 (# s'#, arr# #) ->
423− case copySmallArray# arr1# 0# arr# 0# size1# s'# of
423+ case recCopyInto (Proxy :: Proxy lhs) lts (Proxy :: Proxy res) arr# s'# of
424424 s''# ->
425− case copySmallArray# arr0# 0# arr# size1# size0# s''# of
425+ case recCopyInto (Proxy :: Proxy rhs) rts (Proxy :: Proxy res) arr# s''# of
426426 s'''# ->
427427 case unsafeFreezeSmallArray# arr# s'''# of
428428 (# s''''#, a# #) -> (# s''''#, MkRec a# #)
@@ -492,7 +492,13 @@ insert (l := v) rts =
492492-- | Alias for 'combine'
493493(++:) ::
494494 forall lhs rhs res.
495− res ~ RecAppend lhs rhs =>
495+ ( KnownNat (RecSize lhs),
496+ KnownNat (RecSize rhs),
497+ KnownNat (RecSize lhs + RecSize rhs),
498+ res ~ RecAppend lhs rhs,
499+ RecCopy lhs lhs res,
500+ RecCopy rhs rhs res
501+ ) =>
496502 Rec lhs ->
497503 Rec rhs ->
498504 Rec res
@@ -566,16 +572,16 @@ class ToJSON a => EncodeField a where
566572
567573instance ToJSON a => EncodeField a where
568574 encodeField = pure . toJSON
569− encodeKV k v = fromText k .= v
575+ encodeKV = (.=)
570576
571577instance {-# OVERLAPS #-} ToJSON a => EncodeField (Maybe a) where
572578 encodeField = fmap toJSON
573579 encodeKV _ Nothing = mempty
574− encodeKV k v = fromText k .= v
580+ encodeKV k v = k .= v
575581
576582recToValue :: forall lts. (RecApply lts lts EncodeField) => JSONOptions -> Rec lts -> Value
577583recToValue options r =
578− object $ catMaybes $ reflectRec @EncodeField Proxy (\k v -> (fromString (fieldTransform options k),) <$> encodeField v) r
584+ object $ catMaybes $ reflectRec @EncodeField Proxy (\k v -> (T.pack (fieldTransform options k),) <$> encodeField v) r
579585
580586recToEncoding :: forall lts. (RecApply lts lts EncodeField) => JSONOptions -> Rec lts -> Encoding
581587recToEncoding options r =
@@ -680,10 +686,10 @@ class FromJSON a => ParseField a where
680686 parseField :: Object -> T.Text -> Parser a
681687
682688instance FromJSON a => ParseField a where
683− parseField o k = o .: fromText k
689+ parseField = (.:)
684690
685691instance {-# OVERLAPS #-} FromJSON a => ParseField (Maybe a) where
686− parseField o k = o .:? fromText k
692+ parseField = (.:?)
687693
688694instance
689695 ( KnownSymbol l,
test/JRecSpec.hs+4−10
@@ -71,17 +71,11 @@ spec = do
7171 it "simple append" $ do
7272 Rec (#a := 1) `append` Rec (#b := 2)
7373 `shouldBe` Rec (#a := 1, #b := 2)
74− Rec () `append` Rec (#b := 2)
75− `shouldBe` Rec (#b := 2)
76− Rec (#a := 1) `append` Rec ()
77− `shouldBe` Rec (#a := 1)
78− Rec (#a := 1, #b := 2) `append` Rec (#c := 3)
79− `shouldBe` Rec (#a := 1, #b := 2, #c := 3)
80− Rec (#a := 1) `append` Rec (#b := 2, #c := 3)
81− `shouldBe` Rec (#a := 1, #b := 2, #c := 3)
82− Rec (#a := 1, #b := 2) `append` Rec (#c := 3, #d := 4)
83− `shouldBe` Rec (#a := 1, #b := 2, #c := 3, #d := 4)
8474 it "append with duplicates" $ do
75+ -- TODO: `append` will not deal with duplicates; based on current
76+ -- implementation, the last value (here, 8) will overwrite all duplicate
77+ -- fields. See `union` if you want to deal with duplicates sensibly.
78+ pendingWith "append should just append?"
8579 let r1 = Rec (#b := 5, #a := 6)
8680 r2 = Rec (#c := 7, #a := 8)
8781 r1 `append` r2