Skip to content

Commit 686c179

Browse files
authored
Builder API (#10)
* Builder API, fix #8 * Make eslint happy
1 parent dcbf67c commit 686c179

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed

src/Data/Record/Builder.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"use strict";
2+
3+
exports.copyRecord = function(rec) {
4+
var copy = {};
5+
for (var key in rec) {
6+
if ({}.hasOwnProperty.call(rec, key)) {
7+
copy[key] = rec[key];
8+
}
9+
}
10+
return copy;
11+
};
12+
13+
exports.unsafeInsert = function(l) {
14+
return function(a) {
15+
return function(rec) {
16+
rec[l] = a;
17+
return rec;
18+
};
19+
};
20+
};
21+
22+
exports.unsafeDelete = function(l) {
23+
return function(rec) {
24+
delete rec[l];
25+
return rec;
26+
};
27+
};
28+
29+
exports.unsafeMerge = function(r1) {
30+
return function(r2) {
31+
var copy = {};
32+
for (var k1 in r2) {
33+
if ({}.hasOwnProperty.call(r2, k1)) {
34+
copy[k1] = r2[k1];
35+
}
36+
}
37+
for (var k2 in r1) {
38+
if ({}.hasOwnProperty.call(r1, k2)) {
39+
copy[k2] = r1[k2];
40+
}
41+
}
42+
return copy;
43+
};
44+
};

src/Data/Record/Builder.purs

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
module Data.Record.Builder
2+
( Builder
3+
, build
4+
, insert
5+
, delete
6+
, merge
7+
) where
8+
9+
import Prelude
10+
11+
import Data.Symbol (class IsSymbol, SProxy, reflectSymbol)
12+
import Type.Row (class RowLacks)
13+
14+
foreign import copyRecord :: forall r1. Record r1 -> Record r1
15+
foreign import unsafeInsert :: forall a r1 r2. String -> a -> Record r1 -> Record r2
16+
foreign import unsafeDelete :: forall r1 r2. String -> Record r1 -> Record r2
17+
foreign import unsafeMerge :: forall r1 r2 r3. Record r1 -> Record r2 -> Record r3
18+
19+
-- | A `Builder` can be used to `build` a record by incrementally adding
20+
-- | fields in-place, instead of using `insert` and repeatedly generating new
21+
-- | immutable records which need to be garbage collected.
22+
-- |
23+
-- | The `Category` instance for `Builder` can be used to compose builders.
24+
-- |
25+
-- | For example:
26+
-- |
27+
-- | ```purescript
28+
-- | build (insert x 42 >>> insert y "testing") {} :: { x :: Int, y :: String }
29+
-- | ```
30+
newtype Builder a b = Builder (a -> b)
31+
32+
-- | Build a record, starting from some other record.
33+
build :: forall r1 r2. Builder (Record r1) (Record r2) -> Record r1 -> Record r2
34+
build (Builder b) r1 = b (copyRecord r1)
35+
36+
derive newtype instance semigroupoidBuilder :: Semigroupoid Builder
37+
derive newtype instance categoryBuilder :: Category Builder
38+
39+
-- | Build by inserting a new field.
40+
insert
41+
:: forall l a r1 r2
42+
. RowCons l a r1 r2
43+
=> RowLacks l r1
44+
=> IsSymbol l
45+
=> SProxy l
46+
-> a
47+
-> Builder (Record r1) (Record r2)
48+
insert l a = Builder \r1 -> unsafeInsert (reflectSymbol l) a r1
49+
50+
-- | Build by deleting an existing field.
51+
delete
52+
:: forall l a r1 r2
53+
. IsSymbol l
54+
=> RowLacks l r1
55+
=> RowCons l a r1 r2
56+
=> SProxy l
57+
-> Builder (Record r2) (Record r1)
58+
delete l = Builder \r2 -> unsafeDelete (reflectSymbol l) r2
59+
60+
-- | Build by merging existing fields from another record.
61+
merge
62+
:: forall r1 r2 r3
63+
. Union r1 r2 r3
64+
=> Record r2
65+
-> Builder (Record r1) (Record r3)
66+
merge r2 = Builder \r1 -> unsafeMerge r1 r2

test/Main.purs

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Prelude
44

55
import Control.Monad.Eff (Eff)
66
import Data.Record (delete, get, insert, modify, set)
7+
import Data.Record.Builder as Builder
78
import Data.Symbol (SProxy(..))
89
import Test.Assert (ASSERT, assert')
910

@@ -22,3 +23,10 @@ main = do
2223
get x (modify x (_ + 1) (set x 0 { x: 42 })) == 1
2324
assert' "delete, get" $
2425
get x (delete y { x: 42, y: 1337 }) == 42
26+
27+
let testBuilder = Builder.build (Builder.insert x 42
28+
>>> Builder.merge { y: true, z: "testing" }
29+
>>> Builder.delete y) {}
30+
31+
assert' "Data.Record.Builder" $
32+
testBuilder.x == 42 && testBuilder.z == "testing"

0 commit comments

Comments
 (0)