git.iko.soy/rowtype-yoga/purescript-record-studio
on main help sign in
36 files changed+1043−402
since splitsnapshots
unifiedsplit
.github/workflows/ci.yml+2−2 LICENCE+16 LICENSE → LICENCES/heterogeneous-extrablatt.LICENCE LICENCES/record-extra.LICENCE+21 README.md+88−11 bower.json+7−8 package.json+1−1 packages.dhall+1−2 spago.dhall+9−9 src/Heterogeneous/Extrablatt/Rec.purs−230 src/Record/Studio.purs+21 src/Record/Studio/Keys.purs+30 src/Record/Studio/Map.purs+82 src/Record/Studio/MapKind.purs+83 src/Record/Studio/MapUniform.purs+47 src/Record/Studio/Merge.purs+28 src/Record/Studio/SameKeys.purs+43 src/Record/Studio/Sequence.purs+83 src/Record/Studio/SequenceUniform.purs+63 src/Record/Studio/Shrink.js+6 src/Record/Studio/Shrink.purs+10 src/Record/Studio/SingletonRecord.js+2 src/Record/Studio/SingletonRecord.purs+78 test.dhall+16 test/Heterogeneous/Extrablatt/RecSpec.purs−136 test/KeysSpec.purs+17 test/Main.purs+3−3 test/MapRecordKindSpec.purs+46 test/MapRecordSpec.purs+45 test/MapUniformRecordSpec.purs+22 test/MergeFlippedSpec.purs+20 test/SameKeysSpec.purs+28 test/SequenceSpec.purs+59 test/SequenceUniformRecordSpec.purs+18 test/ShrinkSpec.purs+24 test/SingletonRecordSpec.purs+24
.github/workflows/ci.yml+2−2
@@ -1,6 +1,6 @@
11name: CI
22
3−on:
3+on:
44 push:
55 branches: [main]
66 pull_request:
@@ -30,4 +30,4 @@ jobs:
3030
3131 - run: spago build
3232
33− - run: spago test --no-install
33+ - run: spago -x test.dhall test
LICENCEnew file+16
@@ -0,0 +1,16 @@
1+MIT No Attribution
2+
3+Copyright 2022 Jan Schulte
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy of this
6+software and associated documentation files (the "Software"), to deal in the Software
7+without restriction, including without limitation the rights to use, copy, modify,
8+merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
9+permit persons to whom the Software is furnished to do so.
10+
11+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
12+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
13+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
14+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
15+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
16+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LICENSE → LICENCES/heterogeneous-extrablatt.LICENCErenamed

The content is the same.

LICENCES/record-extra.LICENCEnew file+21
@@ -0,0 +1,21 @@
1+The MIT License (MIT)
2+
3+Copyright (c) 2017 Justin Woo, Adam Saleh
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
README.md+88−11
@@ -1,12 +1,69 @@
1−# purescript-heterogeneous-extrablatt 📰
1+# purescript-record-studio 📀📀📀
22
3−Some additions of common use-cases when working with records and purescript-heterogeneous.
3+You finally scored a record deal.
44
5+## Usage guide
56
6−## Usage guide
7+### Assert two records have the same keys with `SameKeys`
78
9+```purescript
10+-- An example of a function that requires `{|r1}` and `{|r2}` to have the same keys
11+sameKeys :: forall r1 r2. SameKeys r1 r2 => {|r1} -> {|r2} -> Unit
12+sameKeys _ _ = unit
13+let _ = sameKeys { b: "hi", a: 4, c: 4 } { b: "hu", a: "ha" }
14+-- Won't compile: The key "c" is missing from the second record
15+```
16+
17+### Merge records with `//`
18+Easily merge two records:
19+
20+```purescript
21+{ a: 5, b: "B" } // { b: false, c: "c" }
22+-- { a: 5, b: false, c: "c" }
23+```
24+
25+This is similar to the `...` operator in ES6:
26+
27+PureScript:
28+```purescript
29+import Record.Studio ((//))
30+rec1 = { a: 1 }
31+rec2 = { b: 4, c: 8 }
32+result = rec1 // rec2
33+```
34+
35+JS:
36+```js
37+const rec1 = { a: 1 }
38+const rec2 = { b: 4, c: 8 }
39+const result = { ...rec1, ...rec2 }
40+```
41+
42+### Shrink records with `shrink`
43+Easily adjust a record with too many keys:
44+```purescript
45+import Record.Studio (shrink)
46+-- We want to call this
47+-- fn :: { a :: Int } -> Int
48+
49+-- We have this
50+myRec :: { a :: Int, b :: String }
51+myRec = { a: 4, b: "Hello!" }
52+
53+-- Use shrink!
54+result = fn (shrink myRec)
55+```
56+
57+### Get a record's keys at runtime with `keys`
58+```purescript
59+import Record.Studio (keys)
60+
61+theKeys :: Array String
62+theKeys = keys { a: 3, b: "ooh" }
63+-- ["a", "b"]
64+```
865
9−### `hsequenceRec`
66+### `sequenceRecord`
1067Recursively sequence a type constructor out of a record.
1168
1269```purescript
@@ -34,12 +91,20 @@ let
3491 }
3592 , h: 10
3693 }
37−(hsequenceRec input) `shouldEqual` expected
94+(sequenceRecord input) `shouldEqual` expected
95+```
96+
97+### `mapUniformRecord`
98+
99+Recursively map a function over a record where all entries have the same value.
100+This is often better at type inference than `mapRecord`
101+```purescript
102+ (mapUniformRecord (_ + 1) { a: 1, b: 2 }) `shouldEqual` { a: 2, b: 3}
38103```
39104
40−### `hmapRec`
105+### `mapRecord`
41106
42−Recursively map a function over a record.
107+Recursively map a function over a record.
43108```purescript
44109let
45110 input :: MapInput
@@ -68,14 +133,14 @@ let
68133 }
69134 , i: "41"
70135 }
71−(hmapRec f input) `shouldEqual` expected
136+(mapRecord f input) `shouldEqual` expected
72137```
73138
74−### `hmapKRec`
139+### `mapRecordKind`
75140
76141Recursively map a natural transformation over a record.
77142
78−```purescript
143+```purescript
79144 let
80145 input :: MapKInput
81146 input =
@@ -103,5 +168,17 @@ Recursively map a natural transformation over a record.
103168 }
104169 , i: Just 40
105170 }
106−(hmapKRec nt input) `shouldEqual` expected
171+(mapRecordKind nt input) `shouldEqual` expected
172+```
173+
174+### `key`
175+
176+Get the only field name of a Record with one field as a `Proxy`
177+
178+```purescript
179+SingletonRecord.key { foo: unit } `shouldEqual` (Proxy :: Proxy "foo")
107180```
181+
182+## Licence
183+This is a fork of [heterogeneous-extrablatt](https://github.com/sigma-andex/purescript-heterogeneous-extrablatt), which is licenced under MIT. See the [original licence](./LICENCES/heterogeneous-extrablatt.LICENCE). This work is similarly licenced under [MIT](./LICENCE).
184+It includes part of [`purescript-record-extra`](https://github.com/justinwoo/purescript-record-extra) as an inline dependency, which is licenced under MIT, see [original licence](./LICENCES/record-extra.LICENCE).
bower.json+7−8
@@ -1,11 +1,11 @@
11{
2− "name": "purescript-heterogeneous-extrablatt",
2+ "name": "purescript-record-studio",
33 "license": [
44 "MIT-0"
55 ],
66 "repository": {
77 "type": "git",
8− "url": "https://github.com/sigma-andex/purescript-heterogeneous-extrablatt.git"
8+ "url": "https://github.com/rowtype-yoga/purescript-record-studio.git"
99 },
1010 "ignore": [
1111 "**/.*",
@@ -14,11 +14,10 @@
1414 "output"
1515 ],
1616 "dependencies": {
17− "purescript-console": "^v5.0.0",
18− "purescript-effect": "^v3.0.0",
19− "purescript-heterogeneous": "^v0.5.0",
20− "purescript-psci-support": "^v5.0.0",
21− "purescript-spec": "^v5.0.0",
22− "purescript-spec-discovery": "^v6.0.0"
17+ "purescript-heterogeneous": "^v0.6.0",
18+ "purescript-lists": "^v7.0.0",
19+ "purescript-prelude": "^v6.0.0",
20+ "purescript-record": "^v4.0.0",
21+ "purescript-typelevel-prelude": "^v7.0.0"
2322 }
2423}
package.json+1−1
@@ -1,5 +1,5 @@
11{
2− "name": "heterogeneous-extra",
2+ "name": "record-studio",
33 "version": "1.0.0",
44 "description": "",
55 "main": "index.js",
packages.dhall+1−2
@@ -1,5 +1,4 @@
1−
21let upstream =
3− https://github.com/purescript/package-sets/releases/download/psc-0.14.0-20210318/packages.dhall sha256:98bbacd65191cef354ecbafa1610be13e183ee130491ab9c0ef6e3d606f781b5
2+ https://github.com/purescript/package-sets/releases/download/psc-0.15.0-20220513/packages.dhall sha256:1ed784f37ae6131d99acd542d058d5ce39954ccaacc3adba5cc7cf1549d2bffa
43
54in upstream
spago.dhall+9−9
@@ -1,14 +1,14 @@
1−{ name = "purescript-heterogeneous-extrablatt"
1+{ name = "record-studio"
22, dependencies =
3− [ "console"
4− , "effect"
5− , "heterogeneous"
6− , "psci-support"
7− , "spec"
8− , "spec-discovery"
3+ [ "heterogeneous"
4+ , "lists"
5+ , "prelude"
6+ , "record"
7+ , "typelevel-prelude"
8+ , "unsafe-coerce"
99 ]
1010, packages = ./packages.dhall
11−, sources = [ "src/**/*.purs", "test/**/*.purs" ]
11+, sources = [ "src/**/*.purs" ]
1212, license = "MIT-0"
13−, repository = "https://github.com/sigma-andex/purescript-heterogeneous-extrablatt.git"
13+, repository = "https://github.com/rowtype-yoga/purescript-record-studio.git"
1414}
src/Heterogeneous/Extrablatt/Rec.pursdeleted−230
@@ -1,230 +0,0 @@
1−module Heterogeneous.Extrablatt.Rec where
2−
3−import Prelude
4−import Data.Symbol (class IsSymbol)
5−import Heterogeneous.Folding (class FoldingWithIndex, class FoldlRecord, class HFoldlWithIndex, hfoldlWithIndex)
6−import Prim.Row as Row
7−import Prim.RowList (class RowToList)
8−import Record.Builder (Builder)
9−import Record.Builder as Builder
10−import Type.Proxy (Proxy)
11−
12−-- Helper for type inference
13−data HSequenceRec (f :: Type -> Type)
14− = HSequenceRec
15−
16−-- Matches if the type of the current field in the record is f a and therefore needs to be sequenced.
17−instance hsequenceRec_1 ::
18− ( Applicative f
19− , IsSymbol sym
20− , Row.Lacks sym rb
21− , Row.Cons sym a rb rc
22− ) =>
23− FoldingWithIndex
24− (HSequenceRec f)
25− (Proxy sym)
26− (f (Builder { | ra } { | rb }))
27− (f a)
28− (f (Builder { | ra } { | rc })) where
29− foldingWithIndex _ prop rin a = (>>>) <$> rin <*> (Builder.insert prop <$> a)
30−
31−-- Matches if the type of the current field in the record is another record and therefore needs to be recursed.
32−else instance hsequenceRec_2 ::
33− ( Applicative f
34− , IsSymbol sym
35− , Row.Lacks sym rb
36− , RowToList x xRL
37− , Row.Cons sym { | y } rb rc
38− , FoldlRecord
39− (HSequenceRec f)
40− (f (Builder (Record ()) (Record ())))
41− xRL
42− x
43− (f (Builder (Record ()) (Record y)))
44− ) =>
45− FoldingWithIndex
46− (HSequenceRec f)
47− (Proxy sym)
48− (f (Builder { | ra } { | rb }))
49− { | x }
50− (f (Builder { | ra } { | rc })) where
51− foldingWithIndex _ prop rin x = (>>>) <$> rin <*> (fx <#> Builder.insert prop)
52− where
53− fx = hsequenceRec x
54−
55−-- Matches if the type of the current field in the record is any other type independent of sequencing.
56−else instance hsequenceRec_3 ::
57− ( Applicative f
58− , IsSymbol sym
59− , Row.Lacks sym rb
60− , Row.Cons sym x rb rc
61− ) =>
62− FoldingWithIndex
63− (HSequenceRec f)
64− (Proxy sym)
65− (f (Builder { | ra } { | rb }))
66− x
67− (f (Builder { | ra } { | rc })) where
68− foldingWithIndex _ prop rin x = (_ >>> Builder.insert prop x) <$> rin
69−
70−-- | Recursively sequence a record. E.g.
71−-- | ```purescript
72−-- | hsequenceRec { a : { b : { c : { d: Just 10, e : Just "hello" }, f : Just true }
73−-- | -- Just { a : { b : { c : { d: 10, e : "hello" }, f : true }
74−-- | ```
75−hsequenceRec ::
76− forall f rin rout.
77− Applicative f =>
78− HFoldlWithIndex (HSequenceRec f) (f (Builder {} {})) { | rin } (f (Builder {} { | rout })) =>
79− { | rin } ->
80− f { | rout }
81−hsequenceRec =
82− map (flip Builder.build {})
83− <<< hfoldlWithIndex (HSequenceRec :: HSequenceRec f) (pure identity :: f (Builder {} {}))
84−
85−-- Helper for type inference
86−data HMapRec a b
87− = HMapRec (a -> b)
88−
89−-- Matches if the type of the current field in the record is a and therefore needs to be mapped.
90−instance hmapRec_1 ::
91− ( IsSymbol sym
92− , Row.Lacks sym rb
93− , Row.Cons sym b rb rc
94− ) =>
95− FoldingWithIndex
96− (HMapRec a b)
97− (Proxy sym)
98− (Builder { | ra } { | rb })
99− a
100− (Builder { | ra } { | rc }) where
101− foldingWithIndex (HMapRec f) prop rin a = (rin >>> Builder.insert prop (f a))
102−
103−-- Matches if the type of the current field in the record is another record and therefore needs to be recursed.
104−else instance hmapRec_2 ::
105− ( IsSymbol sym
106− , Row.Lacks sym rb
107− , RowToList x xRL
108− , Row.Cons sym { | y } rb rc
109− , FoldlRecord
110− (HMapRec a b)
111− (Builder (Record ()) (Record ()))
112− xRL
113− x
114− (Builder (Record ()) (Record y))
115− ) =>
116− FoldingWithIndex
117− (HMapRec a b)
118− (Proxy sym)
119− (Builder { | ra } { | rb })
120− { | x }
121− (Builder { | ra } { | rc }) where
122− foldingWithIndex (HMapRec f) prop rin x = (rin >>> Builder.insert prop fx)
123− where
124− fx = hmapRec f x
125−
126−-- Matches if the type of the current field in the record is any other type independent of mapping.
127−else instance hmapRec_3 ::
128− ( IsSymbol sym
129− , Row.Lacks sym rb
130− , Row.Cons sym x rb rc
131− ) =>
132− FoldingWithIndex
133− (HMapRec a b)
134− (Proxy sym)
135− (Builder { | ra } { | rb })
136− x
137− (Builder { | ra } { | rc }) where
138− foldingWithIndex _ prop rin x = (rin >>> Builder.insert prop x)
139−
140−-- | Recursively maps a record using a function f.
141−-- | ```purescript
142−-- | let
143−-- | f :: Int -> String
144−-- | f i = show (i + 1)
145−-- | hmapRec f { a : { b : 10, c : { d: 20, e : Just "hello" }}, f : 30 }
146−-- | -- { a : { b : "11", c : { d: "21", e : Just "hello" }, f : "31" }
147−-- | ```
148−hmapRec ::
149− forall a b rin rout.
150− HFoldlWithIndex (HMapRec a b) (Builder {} {}) { | rin } (Builder {} { | rout }) =>
151− (a -> b) ->
152− { | rin } ->
153− { | rout }
154−hmapRec f =
155− (flip Builder.build {})
156− <<< hfoldlWithIndex (HMapRec f :: HMapRec a b) (identity :: Builder {} {})
157−
158−-- Helper for type inference
159−data HMapKRec :: forall k. (k -> Type) -> (k -> Type) -> Type
160−data HMapKRec f g
161− = HMapKRec (f ~> g)
162−
163−-- Matches if the type of the current field in the record is f a and therefore needs to be naturally transformed.
164−instance hmapKRec_1 ::
165− ( IsSymbol sym
166− , Row.Lacks sym rb
167− , Row.Cons sym (g a) rb rc
168− ) =>
169− FoldingWithIndex
170− (HMapKRec f g)
171− (Proxy sym)
172− (Builder { | ra } { | rb })
173− (f a)
174− (Builder { | ra } { | rc }) where
175− foldingWithIndex (HMapKRec nt) prop rin fa = (rin >>> Builder.insert prop (nt fa))
176−
177−-- Matches if the type of the current field in the record is another record and therefore needs to be recursed.
178−else instance hmapKRec_2 ::
179− ( IsSymbol sym
180− , Row.Lacks sym rb
181− , RowToList x xRL
182− , Row.Cons sym { | y } rb rc
183− , FoldlRecord
184− (HMapKRec f g)
185− (Builder (Record ()) (Record ()))
186− xRL
187− x
188− (Builder (Record ()) (Record y))
189− ) =>
190− FoldingWithIndex
191− (HMapKRec f g)
192− (Proxy sym)
193− (Builder { | ra } { | rb })
194− { | x }
195− (Builder { | ra } { | rc }) where
196− foldingWithIndex (HMapKRec nt) prop rin x = (rin >>> Builder.insert prop fx)
197− where
198− fx = hmapKRec nt x
199−
200−-- Matches if the type of the current field in the record is any other type independent of the natural transformation.
201−else instance hmapKRec_3 ::
202− ( IsSymbol sym
203− , Row.Lacks sym rb
204− , Row.Cons sym x rb rc
205− ) =>
206− FoldingWithIndex
207− (HMapKRec f g)
208− (Proxy sym)
209− (Builder { | ra } { | rb })
210− x
211− (Builder { | ra } { | rc }) where
212− foldingWithIndex _ prop rin x = (rin >>> Builder.insert prop x)
213−
214−-- | Recursively mapK a record using a natural transformation. E.g.
215−-- | ```purescript
216−-- | let
217−-- | nt :: Either String ~> Maybe
218−-- | nt = hush
219−-- | hmapKRec { a : { b : { c : { d: Right 10, e : Left "hello" }, f : Right true }
220−-- | -- Just { a : { b : { c : { d: Just 10, e : Nothing }, f : Just true }
221−-- | ```
222−hmapKRec ::
223− forall f g rin rout.
224− HFoldlWithIndex (HMapKRec f g) (Builder {} {}) { | rin } (Builder {} { | rout }) =>
225− (f ~> g) ->
226− { | rin } ->
227− { | rout }
228−hmapKRec nt =
229− (flip Builder.build {})
230− <<< hfoldlWithIndex (HMapKRec nt :: HMapKRec f g) (identity :: Builder {} {})
src/Record/Studio.pursnew file+21
@@ -0,0 +1,21 @@
1+module Record.Studio
2+ ( module Record.Studio.Sequence
3+ , module Record.Studio.Map
4+ , module Record.Studio.MapKind
5+ , module Record.Studio.MapUniform
6+ , module Record.Studio.Merge
7+ , module Record.Studio.Keys
8+ , module Record.Studio.Shrink
9+ , module Record.Studio.SameKeys
10+ , module Record.Studio.SingletonRecord
11+ ) where
12+
13+import Record.Studio.Sequence (SequenceRecord(..), sequenceRecord)
14+import Record.Studio.Map (MapRecord(..), mapRecord)
15+import Record.Studio.MapKind (MapRecordKind(..), mapRecordKind)
16+import Record.Studio.MapUniform (class MapUniformRecord, mapUniformRecord, mapUniformRecordBuilder)
17+import Record.Studio.Keys (class Keys, keys, recordKeys)
18+import Record.Studio.Shrink (shrink)
19+import Record.Studio.Merge (mergeFlipped, (//))
20+import Record.Studio.SameKeys (class SameKeys)
21+import Record.Studio.SingletonRecord (class SingletonRecord, key, value)
src/Record/Studio/Keys.pursnew file+30
@@ -0,0 +1,30 @@
1+module Record.Studio.Keys (class Keys, keys, class KeysRL, keysImpl, recordKeys) where
2+
3+import Prelude
4+
5+import Data.List (List, (:))
6+import Data.List as List
7+import Data.Symbol (class IsSymbol, reflectSymbol)
8+import Prim.RowList as RL
9+import Type.Proxy (Proxy(..))
10+
11+class Keys (r :: Row Type) where
12+ keys :: (Proxy r) -> Array String
13+
14+instance (RL.RowToList r rl, KeysRL rl) => Keys r where
15+ keys _ = List.toUnfoldable $ keysImpl (Proxy :: _ rl)
16+
17+class KeysRL (xs :: RL.RowList Type) where
18+ keysImpl :: Proxy xs -> List String
19+
20+instance KeysRL RL.Nil where
21+ keysImpl _ = mempty
22+
23+instance (IsSymbol name, KeysRL tail) => KeysRL (RL.Cons name ty tail) where
24+ keysImpl _ = first : rest
25+ where
26+ first = reflectSymbol (Proxy :: _ name)
27+ rest = keysImpl (Proxy :: _ tail)
28+
29+recordKeys :: forall r. Keys r => { | r } -> Array String
30+recordKeys _ = keys (Proxy :: _ r)
src/Record/Studio/Map.pursnew file+82
@@ -0,0 +1,82 @@
1+module Record.Studio.Map where
2+
3+import Prelude
4+import Data.Symbol (class IsSymbol)
5+import Heterogeneous.Folding (class FoldingWithIndex, class FoldlRecord, class HFoldlWithIndex, hfoldlWithIndex)
6+import Prim.Row as Row
7+import Prim.RowList (class RowToList)
8+import Record.Builder (Builder)
9+import Record.Builder as Builder
10+import Type.Proxy (Proxy)
11+
12+-- Helper for type inference
13+data MapRecord a b = MapRecord (a -> b)
14+
15+-- Matches if the type of the current field in the record is a and therefore needs to be mapped.
16+instance
17+ ( IsSymbol sym
18+ , Row.Lacks sym rb
19+ , Row.Cons sym b rb rc
20+ ) =>
21+ FoldingWithIndex
22+ (MapRecord a b)
23+ (Proxy sym)
24+ (Builder { | ra } { | rb })
25+ a
26+ (Builder { | ra } { | rc }) where
27+ foldingWithIndex (MapRecord f) prop rin a = (rin >>> Builder.insert prop (f a))
28+
29+-- Matches if the type of the current field in the record is another record and therefore needs to be recursed.
30+else instance
31+ ( IsSymbol sym
32+ , Row.Lacks sym rb
33+ , RowToList x xRL
34+ , Row.Cons sym { | y } rb rc
35+ , FoldlRecord
36+ (MapRecord a b)
37+ (Builder (Record ()) (Record ()))
38+ xRL
39+ x
40+ (Builder (Record ()) (Record y))
41+ ) =>
42+ FoldingWithIndex
43+ (MapRecord a b)
44+ (Proxy sym)
45+ (Builder { | ra } { | rb })
46+ { | x }
47+ (Builder { | ra } { | rc }) where
48+ foldingWithIndex (MapRecord f) prop rin x = (rin >>> Builder.insert prop fx)
49+ where
50+ fx = mapRecord f x
51+
52+-- Matches if the type of the current field in the record is any other type independent of mapping.
53+else instance
54+ ( IsSymbol sym
55+ , Row.Lacks sym rb
56+ , Row.Cons sym x rb rc
57+ ) =>
58+ FoldingWithIndex
59+ (MapRecord a b)
60+ (Proxy sym)
61+ (Builder { | ra } { | rb })
62+ x
63+ (Builder { | ra } { | rc }) where
64+ foldingWithIndex _ prop rin x = (rin >>> Builder.insert prop x)
65+
66+-- | Recursively maps a record using a function f.
67+-- | ```purescript
68+-- | let
69+-- | f :: Int -> String
70+-- | f i = show (i + 1)
71+-- | mapRecord f { a : { b : 10, c : { d: 20, e : Just "hello" }}, f : 30 }
72+-- | -- { a : { b : "11", c : { d: "21", e : Just "hello" }, f : "31" }
73+-- | ```
74+mapRecord
75+ :: forall a b rin rout
76+ . HFoldlWithIndex (MapRecord a b) (Builder {} {}) { | rin } (Builder {} { | rout })
77+ => (a -> b)
78+ -> { | rin }
79+ -> { | rout }
80+mapRecord f =
81+ (flip Builder.build {})
82+ <<< hfoldlWithIndex (MapRecord f :: MapRecord a b) (identity :: Builder {} {})
src/Record/Studio/MapKind.pursnew file+83
@@ -0,0 +1,83 @@
1+module Record.Studio.MapKind where
2+
3+import Prelude
4+import Data.Symbol (class IsSymbol)
5+import Heterogeneous.Folding (class FoldingWithIndex, class FoldlRecord, class HFoldlWithIndex, hfoldlWithIndex)
6+import Prim.Row as Row
7+import Prim.RowList (class RowToList)
8+import Record.Builder (Builder)
9+import Record.Builder as Builder
10+import Type.Proxy (Proxy)
11+
12+-- Helper for type inference
13+data MapRecordKind :: forall k. (k -> Type) -> (k -> Type) -> Type
14+data MapRecordKind f g = MapRecordKind (f ~> g)
15+
16+-- Matches if the type of the current field in the record is f a and therefore needs to be naturally transformed.
17+instance
18+ ( IsSymbol sym
19+ , Row.Lacks sym rb
20+ , Row.Cons sym (g a) rb rc
21+ ) =>
22+ FoldingWithIndex
23+ (MapRecordKind f g)
24+ (Proxy sym)
25+ (Builder { | ra } { | rb })
26+ (f a)
27+ (Builder { | ra } { | rc }) where
28+ foldingWithIndex (MapRecordKind nt) prop rin fa = (rin >>> Builder.insert prop (nt fa))
29+
30+-- Matches if the type of the current field in the record is another record and therefore needs to be recursed.
31+else instance
32+ ( IsSymbol sym
33+ , Row.Lacks sym rb
34+ , RowToList x xRL
35+ , Row.Cons sym { | y } rb rc
36+ , FoldlRecord
37+ (MapRecordKind f g)
38+ (Builder (Record ()) (Record ()))
39+ xRL
40+ x
41+ (Builder (Record ()) (Record y))
42+ ) =>
43+ FoldingWithIndex
44+ (MapRecordKind f g)
45+ (Proxy sym)
46+ (Builder { | ra } { | rb })
47+ { | x }
48+ (Builder { | ra } { | rc }) where
49+ foldingWithIndex (MapRecordKind nt) prop rin x = (rin >>> Builder.insert prop fx)
50+ where
51+ fx = mapRecordKind nt x
52+
53+-- Matches if the type of the current field in the record is any other type independent of the natural transformation.
54+else instance
55+ ( IsSymbol sym
56+ , Row.Lacks sym rb
57+ , Row.Cons sym x rb rc
58+ ) =>
59+ FoldingWithIndex
60+ (MapRecordKind f g)
61+ (Proxy sym)
62+ (Builder { | ra } { | rb })
63+ x
64+ (Builder { | ra } { | rc }) where
65+ foldingWithIndex _ prop rin x = (rin >>> Builder.insert prop x)
66+
67+-- | Recursively mapK a record using a natural transformation. E.g.
68+-- | ```purescript
69+-- | let
70+-- | nt :: Either String ~> Maybe
71+-- | nt = hush
72+-- | mapRecordKind { a : { b : { c : { d: Right 10, e : Left "hello" }, f : Right true }
73+-- | -- Just { a : { b : { c : { d: Just 10, e : Nothing }, f : Just true }
74+-- | ```
75+mapRecordKind
76+ :: forall f g rin rout
77+ . HFoldlWithIndex (MapRecordKind f g) (Builder {} {}) { | rin } (Builder {} { | rout })
78+ => (f ~> g)
79+ -> { | rin }
80+ -> { | rout }
81+mapRecordKind nt =
82+ (flip Builder.build {})
83+ <<< hfoldlWithIndex (MapRecordKind nt :: MapRecordKind f g) (identity :: Builder {} {})
src/Record/Studio/MapUniform.pursnew file+47
@@ -0,0 +1,47 @@
1+module Record.Studio.MapUniform where
2+
3+import Prelude
4+
5+import Data.Symbol (class IsSymbol)
6+import Prim.Row as Row
7+import Prim.RowList (class RowToList, RowList)
8+import Prim.RowList as RL
9+import Record as R
10+import Record.Builder (Builder)
11+import Record.Builder as Builder
12+import Type.Proxy (Proxy(..))
13+
14+mapUniformRecord
15+ :: forall row xs a b row'
16+ . RowToList row xs
17+ => MapUniformRecord xs row a b () row'
18+ => (a -> b)
19+ -> Record row
20+ -> Record row'
21+mapUniformRecord fn = Builder.buildFromScratch
22+ <<< mapUniformRecordBuilder (Proxy :: Proxy xs) fn
23+
24+class
25+ MapUniformRecord (xs :: RowList Type) (row :: Row Type) a b (from :: Row Type) (to :: Row Type)
26+ | xs -> row a b from to where
27+ mapUniformRecordBuilder :: Proxy xs -> (a -> b) -> Record row -> Builder { | from } { | to }
28+
29+-- Start the recursion
30+instance MapUniformRecord RL.Nil row a b () () where
31+ mapUniformRecordBuilder _ _ _ = identity
32+
33+instance
34+ ( IsSymbol name
35+ , Row.Cons name a rest row
36+ , Row.Lacks name from'
37+ , Row.Cons name b from' to
38+ , MapUniformRecord tail row a b from from'
39+ ) =>
40+ MapUniformRecord (RL.Cons name a tail) row a b from to where
41+ mapUniformRecordBuilder _ fn r = first <<< rest
42+ where
43+ first = Builder.insert nameP value
44+ rest = mapUniformRecordBuilder tailP fn r
45+ value = fn (R.get nameP r)
46+ nameP = Proxy :: Proxy name
47+ tailP = Proxy :: Proxy tail
src/Record/Studio/Merge.pursnew file+28
@@ -0,0 +1,28 @@
1+module Record.Studio.Merge where
2+
3+import Prelude
4+
5+import Prim.Row (class Nub, class Union)
6+import Record (merge)
7+
8+-- | Like `merge` but with its arguments flipped. I.e. merges two records with the seconds record's labels taking precedence in the
9+-- | case of overlaps.
10+-- |
11+-- | For example:
12+-- |
13+-- | ```purescript
14+-- | mergeFlipped { x: 1, y: "y" } { y: 2, z: true }
15+-- | = { x: 1, y: 2, z: true }
16+-- | ```
17+mergeFlipped
18+ :: forall r1 r2 r3 r4
19+ . Union r1 r2 r3
20+ => Nub r3 r4
21+ => Record r2
22+ -> Record r1
23+ -> Record r4
24+mergeFlipped = flip merge
25+
26+-- | `record1 // record2` is equivalent to JS's
27+-- | `{ ...record1, ...record2 }`
28+infixr 1 mergeFlipped as //
src/Record/Studio/SameKeys.pursnew file+43
@@ -0,0 +1,43 @@
1+module Record.Studio.SameKeys (class SameKeys, class SameKeysRL) where
2+
3+import Data.Symbol (class IsSymbol)
4+import Prim.Row (class Lacks)
5+import Prim.RowList (class RowToList)
6+import Prim.RowList as RL
7+import Prim.TypeError (class Fail, Beside, Quote, Text)
8+import Type.RowList (class ListToRow)
9+
10+class SameKeys (r1 :: Row Type) (r2 :: Row Type)
11+
12+instance (RowToList r1 rl1, RowToList r2 rl2, SameKeysRL rl1 rl2) => SameKeys r1 r2
13+
14+class SameKeysRL (xs :: RL.RowList Type) (ys :: RL.RowList Type)
15+
16+instance SameKeysRL RL.Nil RL.Nil
17+instance
18+ ( IsSymbol name
19+ , SameKeysRL tail1 tail2
20+ ) =>
21+ SameKeysRL (RL.Cons name ty1 tail1) (RL.Cons name ty2 tail2)
22+else instance
23+ ( ListToRow RL.Nil r
24+ , Fail (Beside (Text "The key ") (Beside (Quote name) (Text " is missing from the second record")))
25+ ) =>
26+ SameKeysRL (RL.Cons name ty tail) RL.Nil
27+else instance
28+ ( ListToRow RL.Nil r
29+ , Fail (Beside (Text "The key ") (Beside (Quote name) (Text " is missing from the first record")))
30+ ) =>
31+ SameKeysRL RL.Nil (RL.Cons name ty tail)
32+else instance
33+ ( ListToRow (RL.Cons name2 ty1 tail1) r
34+ , Lacks name1 r
35+ , Fail (Beside (Text "The key ") (Beside (Quote name1) (Text " is missing from the second record")))
36+ ) =>
37+ SameKeysRL (RL.Cons name1 ty1 tail1) (RL.Cons name2 ty2 tail2)
38+else instance
39+ ( ListToRow (RL.Cons name1 ty1 tail1) r
40+ , Lacks name2 r
41+ , Fail (Beside (Text "The key ") (Beside (Quote name2) (Text " is missing from the first record")))
42+ ) =>
43+ SameKeysRL (RL.Cons name1 ty1 tail1) (RL.Cons name2 ty2 tail2)
src/Record/Studio/Sequence.pursnew file+83
@@ -0,0 +1,83 @@
1+module Record.Studio.Sequence where
2+
3+import Prelude
4+
5+import Data.Symbol (class IsSymbol)
6+import Heterogeneous.Folding (class FoldingWithIndex, class FoldlRecord, class HFoldlWithIndex, hfoldlWithIndex)
7+import Prim.Row as Row
8+import Prim.RowList (class RowToList)
9+import Record.Builder (Builder)
10+import Record.Builder as Builder
11+import Type.Proxy (Proxy)
12+
13+-- Helper for type inference
14+data SequenceRecord (f :: Type -> Type) = SequenceRecord
15+
16+-- Matches if the type of the current field in the record is f a and therefore needs to be sequenced.
17+instance
18+ ( Applicative f
19+ , IsSymbol sym
20+ , Row.Lacks sym rb
21+ , Row.Cons sym a rb rc
22+ ) =>
23+ FoldingWithIndex
24+ (SequenceRecord f)
25+ (Proxy sym)
26+ (f (Builder { | ra } { | rb }))
27+ (f a)
28+ (f (Builder { | ra } { | rc })) where
29+ foldingWithIndex _ prop rin a = (>>>) <$> rin <*> (Builder.insert prop <$> a)
30+
31+-- Matches if the type of the current field in the record is another record and therefore needs to be recursed.
32+else instance
33+ ( Applicative f
34+ , IsSymbol sym
35+ , Row.Lacks sym rb
36+ , RowToList x xRL
37+ , Row.Cons sym { | y } rb rc
38+ , FoldlRecord
39+ (SequenceRecord f)
40+ (f (Builder (Record ()) (Record ())))
41+ xRL
42+ x
43+ (f (Builder (Record ()) (Record y)))
44+ ) =>
45+ FoldingWithIndex
46+ (SequenceRecord f)
47+ (Proxy sym)
48+ (f (Builder { | ra } { | rb }))
49+ { | x }
50+ (f (Builder { | ra } { | rc })) where
51+ foldingWithIndex _ prop rin x = (>>>) <$> rin <*> (fx <#> Builder.insert prop)
52+ where
53+ fx = sequenceRecord x
54+
55+-- Matches if the type of the current field in the record is any other type independent of sequencing.
56+else instance
57+ ( Applicative f
58+ , IsSymbol sym
59+ , Row.Lacks sym rb
60+ , Row.Cons sym x rb rc
61+ ) =>
62+ FoldingWithIndex
63+ (SequenceRecord f)
64+ (Proxy sym)
65+ (f (Builder { | ra } { | rb }))
66+ x
67+ (f (Builder { | ra } { | rc })) where
68+ foldingWithIndex _ prop rin x = (_ >>> Builder.insert prop x) <$> rin
69+
70+-- | Recursively sequence a record. E.g.
71+-- | ```purescript
72+-- | sequenceRecord { a : { b : { c : { d: Just 10, e : Just "hello" }, f : Just true }
73+-- | -- Just { a : { b : { c : { d: 10, e : "hello" }, f : true }
74+-- | ```
75+sequenceRecord
76+ :: forall f rin rout
77+ . Applicative f
78+ => HFoldlWithIndex (SequenceRecord f) (f (Builder {} {})) { | rin } (f (Builder {} { | rout }))
79+ => { | rin }
80+ -> f { | rout }
81+sequenceRecord =
82+ map (flip Builder.build {})
83+ <<< hfoldlWithIndex (SequenceRecord :: SequenceRecord f) (pure identity :: f (Builder {} {}))
src/Record/Studio/SequenceUniform.pursnew file+63
@@ -0,0 +1,63 @@
1+module Record.Studio.SequenceUniform where
2+
3+import Prelude
4+
5+import Data.Symbol (class IsSymbol)
6+import Prim.Row as Row
7+import Prim.RowList as RL
8+import Record as R
9+import Record.Builder (Builder)
10+import Record.Builder as Builder
11+import Type.Proxy (Proxy(..))
12+
13+sequenceUniformRecord
14+ :: forall row row' rl m
15+ . RL.RowToList row rl
16+ => SequenceUniformRecord rl row () row' m
17+ => Record row
18+ -> m (Record row')
19+sequenceUniformRecord a = Builder.build <@> {} <$> builder
20+ where
21+ builder = sequenceUniformRecordImpl (Proxy :: _ rl) a
22+
23+class
24+ Functor m <=
25+ SequenceUniformRecord (rl :: RL.RowList Type) row from to m
26+ | rl -> row from to m
27+ where
28+ sequenceUniformRecordImpl :: Proxy rl -> Record row -> m (Builder { | from } { | to })
29+
30+instance
31+ ( IsSymbol name
32+ , Row.Cons name (m ty) trash row
33+ , Functor m
34+ , Row.Lacks name ()
35+ , Row.Cons name ty () to
36+ ) =>
37+ SequenceUniformRecord (RL.Cons name (m ty) RL.Nil) row () to m where
38+ sequenceUniformRecordImpl _ a =
39+ Builder.insert namep <$> valA
40+ where
41+ namep = Proxy :: _ name
42+ valA = R.get namep a
43+
44+else instance
45+ ( IsSymbol name
46+ , Row.Cons name (m ty) trash row
47+ , Apply m
48+ , SequenceUniformRecord tail row from from' m
49+ , Row.Lacks name from'
50+ , Row.Cons name ty from' to
51+ ) =>
52+ SequenceUniformRecord (RL.Cons name (m ty) tail) row from to m where
53+ sequenceUniformRecordImpl _ a =
54+ fn <$> valA <*> rest
55+ where
56+ namep = Proxy :: _ name
57+ valA = R.get namep a
58+ tailp = Proxy :: _ tail
59+ rest = sequenceUniformRecordImpl tailp a
60+ fn valA' rest' = Builder.insert namep valA' <<< rest'
61+
62+instance Applicative m => SequenceUniformRecord RL.Nil row () () m where
63+ sequenceUniformRecordImpl _ _ = pure identity
src/Record/Studio/Shrink.jsnew file+6
@@ -0,0 +1,6 @@
1+export const shrinkImpl = keys => record => {
2+ return keys.reduce((acc, key) => {
3+ acc[key] = record[key];
4+ return acc;
5+ }, {});
6+}
src/Record/Studio/Shrink.pursnew file+10
@@ -0,0 +1,10 @@
1+module Record.Studio.Shrink (shrink) where
2+
3+import Prim.Row (class Union)
4+import Record.Studio.Keys (class Keys, keys)
5+import Type.Proxy (Proxy(..))
6+
7+foreign import shrinkImpl :: forall r1 r2. Array String -> Record r1 -> Record r2
8+
9+shrink :: forall a b r. Union b r a => Keys b => { | a } -> { | b }
10+shrink = shrinkImpl (keys (Proxy :: Proxy b))
src/Record/Studio/SingletonRecord.jsnew file+2
@@ -0,0 +1,2 @@
1+
2+export const unsafeGetFirstField = rec => Object.values(rec)[0]
src/Record/Studio/SingletonRecord.pursnew file+78
@@ -0,0 +1,78 @@
1+module Record.Studio.SingletonRecord
2+ ( class SingletonRecord
3+ , class SingletonRecordFields
4+ , key
5+ , value
6+ , singletonRecordFields
7+ ) where
8+
9+import Prelude
10+
11+import Data.Symbol (class IsSymbol)
12+import Prim.Row as Row
13+import Prim.RowList (class RowToList)
14+import Prim.RowList as RL
15+import Prim.TypeError (class Fail, Beside, Quote, QuoteLabel, Text)
16+import Type.Proxy (Proxy(..))
17+import Unsafe.Coerce (unsafeCoerce)
18+
19+class SingletonRecord :: forall k1 k2. k1 -> Type -> Row Type -> k2 -> Constraint
20+class SingletonRecord key value rec recRL | rec -> key value recRL where
21+ -- | Get the key of a record with only one field as a `Proxy`
22+ key :: Record rec -> Proxy key
23+ value :: Record rec -> value
24+
25+foreign import unsafeGetFirstField :: forall r a. { | r } -> a
26+
27+instance
28+ ( RowToList rec recRL
29+ , SingletonRecordFields key a rec recRL
30+ ) =>
31+ SingletonRecord key a rec rl where
32+ key = singletonRecordFields (Proxy :: Proxy recRL)
33+ value = unsafeGetFirstField
34+else instance (Fail (Text "The record must have exactly one field")) => SingletonRecord key a rec recRL where
35+ key _ = unsafeCoerce unit
36+ value _ = unsafeCoerce unit
37+
38+class SingletonRecordFields :: forall k1 k2. k1 -> Type -> Row Type -> k2 -> Constraint
39+class
40+ SingletonRecordFields key value rec recRL
41+ | rec -> key value
42+ where
43+ singletonRecordFields :: Proxy recRL -> { | rec } -> Proxy key
44+
45+instance
46+ ( RowToList rec (RL.Cons key a RL.Nil)
47+ , Row.Cons key a () rec
48+ , IsSymbol key
49+ ) =>
50+ SingletonRecordFields key a rec (RL.Cons key a RL.Nil) where
51+ singletonRecordFields _ _ = (Proxy :: Proxy key)
52+
53+instance
54+ ( Fail (Beside ErrorMsg (Text "instead of {}"))
55+ ) =>
56+ SingletonRecordFields key a rec RL.Nil where
57+ singletonRecordFields _ _ = unsafeCoerce unit
58+
59+else instance
60+ ( Fail
61+ ( ErrorMsg
62+ ++ ButReceivedStart
63+ ++ (QuoteLabel key ++ DblCol ++ Quote a)
64+ ++ Comma
65+ ++ (QuoteLabel key1 ++ DblCol ++ Quote a1)
66+ ++ ButReceivedEnd
67+ )
68+ ) =>
69+ SingletonRecordFields key a rec (RL.Cons key a (RL.Cons key1 a1 rl)) where
70+ singletonRecordFields _ _ = unsafeCoerce unit
71+
72+type ErrorMsg = (Text "Must provide a record with exactly one field ")
73+type ButReceivedStart = (Text "instead of { ")
74+type ButReceivedEnd = (Text ", ... }")
75+type DblCol = (Text " :: ")
76+type Comma = (Text ", ")
77+
78+infixl 3 type Beside as ++
test.dhallnew file+16
@@ -0,0 +1,16 @@
1+let conf = ./spago.dhall
2+
3+in conf
4+ // { sources = conf.sources # [ "test/**/*.purs" ]
5+ , dependencies =
6+ conf.dependencies
7+ # [ "aff"
8+ , "effect"
9+ , "spec"
10+ , "spec-discovery"
11+ , "either"
12+ , "maybe"
13+ , "foreign-object"
14+ , "unsafe-coerce"
15+ ]
16+ }
test/Heterogeneous/Extrablatt/RecSpec.pursdeleted−136
@@ -1,136 +0,0 @@
1−module Heterogeneous.Extrablatt.RecSpec where
2−
3−import Prelude
4−
5−import Data.Either (Either(..), hush)
6−import Data.Maybe (Maybe(..))
7−import Heterogeneous.Extrablatt.Rec (hmapRec, hmapKRec, hsequenceRec)
8−import Test.Spec (Spec, describe, it)
9−import Test.Spec.Assertions (shouldEqual)
10−
11−type SequenceInput
12− = { a :: Maybe Int, b :: String, c :: { d :: Maybe String, e :: Boolean, f :: { g :: Maybe Boolean } }, h :: Int }
13−
14−type SequenceOutput
15− = { a :: Int, b :: String, c :: { d :: String, e :: Boolean, f :: { g :: Boolean } }, h :: Int }
16−
17−type MapInput
18− = { a :: Int, b :: String, c :: { d :: Maybe String, e :: Int, f :: { g :: Boolean, h :: Int } }, i :: Int }
19−
20−type MapOutput
21− = { a :: String, b :: String, c :: { d :: Maybe String, e :: String, f :: { g :: Boolean, h :: String } }, i :: String }
22−
23−type MapKInput
24− = { a :: Either String Int, b :: String, c :: { d :: Either String String, e :: Int, f :: { g :: Either String Boolean, h :: Either String Int } }, i :: Maybe Int }
25−
26−type MapKOutput
27− = { a :: Maybe Int, b :: String, c :: { d :: Maybe String, e :: Int, f :: { g :: Maybe Boolean, h :: Maybe Int } }, i :: Maybe Int }
28−
29−spec :: Spec Unit
30−spec =
31− describe "Heterogeneous.Extrablatt.Rec" do
32− describe "hsequenceRec" do
33− it "should recursively sequence a valid record" do
34− let
35− input :: SequenceInput
36− input =
37− { a: Just 10
38− , b: "hello"
39− , c:
40− { d: Just "world"
41− , e: true
42− , f: { g: Just true }
43− }
44− , h: 10
45− }
46−
47− expected :: Maybe SequenceOutput
48− expected = Just
49− { a: 10
50− , b: "hello"
51− , c:
52− { d: "world"
53− , e: true
54− , f: { g: true }
55− }
56− , h: 10
57− }
58− (hsequenceRec input) `shouldEqual` expected
59− it "should recursively sequence an invalid record" do
60− let
61− input :: SequenceInput
62− input =
63− { a: Just 10
64− , b: "hello"
65− , c:
66− { d: Just "world"
67− , e: true
68− , f: { g: Nothing }
69− }
70− , h: 10
71− }
72−
73− expected :: Maybe SequenceOutput
74− expected = Nothing
75− (hsequenceRec input) `shouldEqual` expected
76−
77− describe "hmapRec" do
78− it "should recursively map a function over a record" do
79− let
80− input :: MapInput
81− input =
82− { a: 10
83− , b: "hello"
84− , c:
85− { d: Just "world"
86− , e: 20
87− , f: { g: true, h: 30 }
88− }
89− , i: 40
90− }
91−
92− f :: Int -> String
93− f i = show (i + 1)
94−
95− expected :: MapOutput
96− expected =
97− { a: "11"
98− , b: "hello"
99− , c:
100− { d: Just "world"
101− , e: "21"
102− , f: { g: true, h: "31" }
103− }
104− , i: "41"
105− }
106− (hmapRec f input) `shouldEqual` expected
107− describe "hmapKRec" do
108− it "should recursively mapK a natural transformation over a record" do
109− let
110− input :: MapKInput
111− input =
112− { a: Right 10
113− , b: "hello"
114− , c:
115− { d: Left "world"
116− , e: 20
117− , f: { g: Right true, h: Left "broken" }
118− }
119− , i: Just 40
120− }
121−
122− nt :: Either String ~> Maybe
123− nt = hush
124−
125− expected :: MapKOutput
126− expected =
127− { a: Just 10
128− , b: "hello"
129− , c:
130− { d: Nothing
131− , e: 20
132− , f: { g: Just true, h: Nothing }
133− }
134− , i: Just 40
135− }
136− (hmapKRec nt input) `shouldEqual` expected
test/KeysSpec.pursnew file+17
@@ -0,0 +1,17 @@
1+module KeysSpec where
2+
3+import Prelude
4+
5+import Record.Studio (recordKeys)
6+import Test.Spec (Spec, describe, it)
7+import Test.Spec.Assertions (shouldEqual)
8+
9+spec :: Spec Unit
10+spec =
11+ describe "keys" do
12+ it "gets the keys of {}" do
13+ recordKeys {} `shouldEqual` []
14+ it "gets the keys of { a :: String }" do
15+ recordKeys { a: "" } `shouldEqual` [ "a" ]
16+ it "gets the keys of { a :: String, b :: Int }" do
17+ recordKeys { a: "", b: 1 } `shouldEqual` [ "a", "b" ]
test/Main.purs+3−3
@@ -9,6 +9,6 @@ import Test.Spec.Reporter.Console (consoleReporter)
99import Test.Spec.Runner (runSpec)
1010
1111main :: Effect Unit
12−main = launchAff_ do
13− specs <- discover "\\..*Spec"
14− runSpec [consoleReporter] specs
12+main = launchAff_ $ discover pattern >>= runSpec [ consoleReporter ]
13+ where
14+ pattern = ".*Spec"
test/MapRecordKindSpec.pursnew file+46
@@ -0,0 +1,46 @@
1+module MapRecordKindSpec where
2+
3+import Prelude
4+
5+import Data.Either (Either(..), hush)
6+import Data.Maybe (Maybe(..))
7+import Record.Studio (mapRecordKind)
8+import Test.Spec (Spec, describe, it)
9+import Test.Spec.Assertions (shouldEqual)
10+
11+type MapKindInput = { a :: Either String Int, b :: String, c :: { d :: Either String String, e :: Int, f :: { g :: Either String Boolean, h :: Either String Int } }, i :: Maybe Int }
12+
13+type MapKindOutput = { a :: Maybe Int, b :: String, c :: { d :: Maybe String, e :: Int, f :: { g :: Maybe Boolean, h :: Maybe Int } }, i :: Maybe Int }
14+
15+spec :: Spec Unit
16+spec =
17+ describe "mapRecordKind" do
18+ it "should recursively a natural transformation over a record" do
19+ let
20+ input :: MapKindInput
21+ input =
22+ { a: Right 10
23+ , b: "hello"
24+ , c:
25+ { d: Left "world"
26+ , e: 20
27+ , f: { g: Right true, h: Left "broken" }
28+ }
29+ , i: Just 40
30+ }
31+
32+ nt :: Either String ~> Maybe
33+ nt = hush
34+
35+ expected :: MapKindOutput
36+ expected =
37+ { a: Just 10
38+ , b: "hello"
39+ , c:
40+ { d: Nothing
41+ , e: 20
42+ , f: { g: Just true, h: Nothing }
43+ }
44+ , i: Just 40
45+ }
46+ (mapRecordKind nt input) `shouldEqual` expected
test/MapRecordSpec.pursnew file+45
@@ -0,0 +1,45 @@
1+module MapRecordSpec where
2+
3+import Prelude
4+
5+import Data.Maybe (Maybe(..))
6+import Record.Studio (mapRecord)
7+import Test.Spec (Spec, describe, it)
8+import Test.Spec.Assertions (shouldEqual)
9+
10+type MapInput = { a :: Int, b :: String, c :: { d :: Maybe String, e :: Int, f :: { g :: Boolean, h :: Int } }, i :: Int }
11+
12+type MapOutput = { a :: String, b :: String, c :: { d :: Maybe String, e :: String, f :: { g :: Boolean, h :: String } }, i :: String }
13+
14+spec :: Spec Unit
15+spec =
16+ describe "mapRecord" do
17+ it "should recursively map a function over a record" do
18+ let
19+ input :: MapInput
20+ input =
21+ { a: 10
22+ , b: "hello"
23+ , c:
24+ { d: Just "world"
25+ , e: 20
26+ , f: { g: true, h: 30 }
27+ }
28+ , i: 40
29+ }
30+
31+ f :: Int -> String
32+ f i = show (i + 1)
33+
34+ expected :: MapOutput
35+ expected =
36+ { a: "11"
37+ , b: "hello"
38+ , c:
39+ { d: Just "world"
40+ , e: "21"
41+ , f: { g: true, h: "31" }
42+ }
43+ , i: "41"
44+ }
45+ (mapRecord f input) `shouldEqual` expected
test/MapUniformRecordSpec.pursnew file+22
@@ -0,0 +1,22 @@
1+module MapUniformRecordSpec where
2+
3+import Prelude
4+
5+import Record.Studio.MapUniform (mapUniformRecord)
6+import Test.Spec (Spec, describe, it)
7+import Test.Spec.Assertions (shouldEqual)
8+
9+spec :: Spec Unit
10+spec =
11+ describe "mapUniformRecord" do
12+ it "should map a function over a uniform record" do
13+ let
14+ input = { a: 10, b: 12 }
15+
16+ f = (_ + 4)
17+
18+ expected =
19+ { a: 14
20+ , b: 16
21+ }
22+ (mapUniformRecord f input) `shouldEqual` expected
test/MergeFlippedSpec.pursnew file+20
@@ -0,0 +1,20 @@
1+module MergeFlippedSpec where
2+
3+import Prelude
4+
5+import Record.Studio ((//))
6+import Test.Spec (Spec, describe, it)
7+import Test.Spec.Assertions (shouldEqual)
8+
9+spec :: Spec Unit
10+spec =
11+ describe "Record Operations" do
12+ describe "mergeFlipped (//)" do
13+ it "should merge disjoint records" do
14+ ({ a: 1, b: 2 } // { c: 3 }) `shouldEqual` { a: 1, b: 2, c: 3 }
15+ it "should prioritise the right-hand-side" do
16+ ({ a: 1, b: 2 } // { b: 3 }) `shouldEqual` { a: 1, b: 3 }
17+ it "should prioritise the right-hand-side type" do
18+ ({ a: 1, b: 2 } // { b: "a" }) `shouldEqual` { a: 1, b: "a" }
19+ it "should work with larger records" do
20+ ({ a: 1, b: 2 } // { c: 3, d: 4 }) `shouldEqual` { a: 1, b: 2, c: 3, d: 4 }
test/SameKeysSpec.pursnew file+28
@@ -0,0 +1,28 @@
1+module SameKeysSpec where
2+
3+import Prelude
4+
5+import Record.Studio.SameKeys (class SameKeys)
6+import Test.Spec (Spec, describe, it)
7+
8+spec :: Spec Unit
9+spec =
10+ describe "same keys" do
11+ it "asserts the same keys" do
12+ -- let _ = sameKeys { b: "hi", a: 4, c: 4 } { b: "hu", a: "ha" }
13+ -- The key "c" is missing from the second record
14+
15+ -- let _ = sameKeys { b: "hi", a: 4 } { b: "hu", a: "ha", c: 4 }
16+ -- The key "c" is missing from the first record
17+
18+ -- let _ = sameKeys { b: "hi", a: 4 } { b: "hu" }
19+ -- The key "a" is missing from the second record
20+
21+ -- let _ = sameKeys { b: "hi", a: 4 } { b: "hu" }
22+ -- The key "a" is missing from the second record
23+
24+ let _ = sameKeys { b: "hi", z: 1 } { z: "", b: { c: false } }
25+ pure unit
26+
27+sameKeys :: forall r1 r2. SameKeys r1 r2 => {|r1} -> {|r2} -> Unit
28+sameKeys _ _ = unit
test/SequenceSpec.pursnew file+59
@@ -0,0 +1,59 @@
1+module SequenceRecordSpec where
2+
3+import Prelude
4+
5+import Data.Maybe (Maybe(..))
6+import Record.Studio (sequenceRecord)
7+import Test.Spec (Spec, describe, it)
8+import Test.Spec.Assertions (shouldEqual)
9+
10+type SequenceInput = { a :: Maybe Int, b :: String, c :: { d :: Maybe String, e :: Boolean, f :: { g :: Maybe Boolean } }, h :: Int }
11+
12+type SequenceOutput = { a :: Int, b :: String, c :: { d :: String, e :: Boolean, f :: { g :: Boolean } }, h :: Int }
13+
14+spec :: Spec Unit
15+spec =
16+ describe "sequenceRecord" do
17+ it "should recursively sequence a valid record" do
18+ let
19+ input :: SequenceInput
20+ input =
21+ { a: Just 10
22+ , b: "hello"
23+ , c:
24+ { d: Just "world"
25+ , e: true
26+ , f: { g: Just true }
27+ }
28+ , h: 10
29+ }
30+
31+ expected :: Maybe SequenceOutput
32+ expected = Just
33+ { a: 10
34+ , b: "hello"
35+ , c:
36+ { d: "world"
37+ , e: true
38+ , f: { g: true }
39+ }
40+ , h: 10
41+ }
42+ (sequenceRecord input) `shouldEqual` expected
43+ it "should recursively sequence an invalid record" do
44+ let
45+ input :: SequenceInput
46+ input =
47+ { a: Just 10
48+ , b: "hello"
49+ , c:
50+ { d: Just "world"
51+ , e: true
52+ , f: { g: Nothing }
53+ }
54+ , h: 10
55+ }
56+
57+ expected :: Maybe SequenceOutput
58+ expected = Nothing
59+ (sequenceRecord input) `shouldEqual` expected
test/SequenceUniformRecordSpec.pursnew file+18
@@ -0,0 +1,18 @@
1+module SequenceUniformRecordSpec where
2+
3+import Prelude
4+
5+import Data.Maybe (Maybe(..))
6+import Record.Studio.SequenceUniform (sequenceUniformRecord)
7+import Test.Spec (Spec, describe, it)
8+import Test.Spec.Assertions (shouldEqual)
9+
10+spec :: Spec Unit
11+spec =
12+ describe "sequenceUniformRecord" do
13+ it "should sequence an homogeneous record" do
14+ let input = { a: Just 4, b: Nothing :: Maybe String }
15+ sequenceUniformRecord input `shouldEqual` Nothing
16+ it "should sequence another homogeneous record" do
17+ let input = { a: Just 4, b: Just "a" }
18+ sequenceUniformRecord input `shouldEqual` (Just { a: 4, b: "a" })
test/ShrinkSpec.pursnew file+24
@@ -0,0 +1,24 @@
1+module ShrinkSpec where
2+
3+import Prelude
4+
5+import Foreign.Object as Object
6+import Record.Studio (shrink)
7+import Test.Spec (Spec, describe, it)
8+import Test.Spec.Assertions (shouldEqual)
9+import Unsafe.Coerce (unsafeCoerce)
10+
11+spec :: Spec Unit
12+spec =
13+ describe "shrink" do
14+ it "shrinks down records all the way" do
15+ shrink { a: 1, b: 2, c: "c" } `shouldEqual` {}
16+ it "shrinks down records a bit" do
17+ shrink { a: 1, b: 2, c: 3, d: 4 } `shouldEqual` { a: 1, b: 2, c: 3 }
18+ it "shrinks to the correct runtime representation" do
19+ let
20+ shrunk :: { c :: Int, e :: Int }
21+ shrunk = shrink { a: 1, b: 2, c: 3, d: 4, e: 5 }
22+ unsafeCoerce shrunk `shouldEqual` Object.fromHomogeneous { c: 3, e: 5 }
23+ it "shrinks heterogeneous records" do
24+ shrink { a: "a", b: { e: 4 }, c: false } `shouldEqual` { c: false }
test/SingletonRecordSpec.pursnew file+24
@@ -0,0 +1,24 @@
1+module SingletonRecordSpec where
2+
3+import Prelude
4+
5+import Record.Studio.SingletonRecord (key, value)
6+import Test.Spec (Spec, describe, it)
7+import Test.Spec.Assertions (shouldEqual)
8+import Type.Proxy (Proxy(..))
9+
10+spec :: Spec Unit
11+spec =
12+ describe "key" do
13+ it "should get the only key of a singleton record as a `Proxy`" do
14+ let
15+ input = { a: 10 }
16+
17+ -- | These should fail to compile with a helpful message
18+ -- inputFails1 = key {}
19+ -- inputFails2 = key { a: 10, b: "a" }
20+ -- inputFails3 = value {}
21+ -- inputFails4 = value { a: 10, b: "a" }
22+
23+ (key input) `shouldEqual` (Proxy :: Proxy "a")
24+ (value input) `shouldEqual` 10