diff --git a/src/Data/Record.purs b/src/Data/Record.purs index 6a5ca55..9048cbf 100644 --- a/src/Data/Record.purs +++ b/src/Data/Record.purs @@ -4,6 +4,7 @@ module Data.Record , modify , insert , delete + , rename , equal , class EqualFields , equalFields @@ -114,6 +115,32 @@ delete -> Record r1 delete l r = runFn2 unsafeDeleteFn (reflectSymbol l) r +-- | Rename a property for a label which is specified using a value-level proxy for +-- | a type-level string. +-- | +-- | Note that the type of the resulting row must _lack_ the specified property. +-- | Since duplicate labels are allowed, this is checked with a type class constraint. +-- | +-- | For example: +-- | +-- | ```purescript +-- | rename (SProxy :: SProxy "x") (SProxy :: SProxy "y") +-- | :: forall a r. RowLacks "x" r => RowLacks "y" r => { x :: a | r} -> { y :: a | r} +-- | ``` +rename :: forall prev next ty input inter output + . IsSymbol prev + => IsSymbol next + => RowCons prev ty inter input + => RowLacks prev inter + => RowCons next ty inter output + => RowLacks next inter + => SProxy prev + -> SProxy next + -> Record input + -> Record output +rename prev next record = + insert next (get prev record) (delete prev record :: Record inter) + -- | Check two records of the same type for equality. equal :: forall r rs diff --git a/src/Data/Record/Builder.js b/src/Data/Record/Builder.js index 6c6134a..7aaec61 100644 --- a/src/Data/Record/Builder.js +++ b/src/Data/Record/Builder.js @@ -26,6 +26,16 @@ exports.unsafeDelete = function(l) { }; }; +exports.unsafeRename = function(l1) { + return function (l2) { + return function (rec) { + rec[l2] = rec[l1]; + delete rec[l1]; + return rec; + }; + }; +}; + exports.unsafeMerge = function(r1) { return function(r2) { var copy = {}; diff --git a/src/Data/Record/Builder.purs b/src/Data/Record/Builder.purs index 76586b6..3de72fe 100644 --- a/src/Data/Record/Builder.purs +++ b/src/Data/Record/Builder.purs @@ -3,6 +3,7 @@ module Data.Record.Builder , build , insert , delete + , rename , merge ) where @@ -14,6 +15,7 @@ import Type.Row (class RowLacks) foreign import copyRecord :: forall r1. Record r1 -> Record r1 foreign import unsafeInsert :: forall a r1 r2. String -> a -> Record r1 -> Record r2 foreign import unsafeDelete :: forall r1 r2. String -> Record r1 -> Record r2 +foreign import unsafeRename :: forall r1 r2. String -> String -> Record r1 -> Record r2 foreign import unsafeMerge :: forall r1 r2 r3. Record r1 -> Record r2 -> Record r3 -- | A `Builder` can be used to `build` a record by incrementally adding @@ -57,6 +59,19 @@ delete -> Builder (Record r2) (Record r1) delete l = Builder \r2 -> unsafeDelete (reflectSymbol l) r2 +-- | Build by renaming an existing field. +rename :: forall l1 l2 a r1 r2 r3 + . IsSymbol l1 + => IsSymbol l2 + => RowCons l1 a r2 r1 + => RowLacks l1 r2 + => RowCons l2 a r2 r3 + => RowLacks l2 r2 + => SProxy l1 + -> SProxy l2 + -> Builder (Record r1) (Record r3) +rename l1 l2 = Builder \r1 -> unsafeRename (reflectSymbol l1) (reflectSymbol l2) r1 + -- | Build by merging existing fields from another record. merge :: forall r1 r2 r3 diff --git a/test/Main.purs b/test/Main.purs index 4cb8a79..1c2a34b 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -3,7 +3,7 @@ module Test.Main where import Prelude import Control.Monad.Eff (Eff) -import Data.Record (delete, get, insert, modify, set, equal) +import Data.Record (delete, equal, get, insert, modify, rename, set) import Data.Record.Builder as Builder import Data.Record.ST (pokeSTRecord, pureSTRecord, thawSTRecord) import Data.Record.Unsafe (unsafeHas) @@ -14,6 +14,7 @@ main :: Eff (assert :: ASSERT) Unit main = do let x = SProxy :: SProxy "x" y = SProxy :: SProxy "y" + z = SProxy :: SProxy "z" assert' "insert, get" $ get x (insert x 42 {}) == 42 @@ -25,6 +26,8 @@ main = do get x (modify x (_ + 1) (set x 0 { x: 42 })) == 1 assert' "delete, get" $ get x (delete y { x: 42, y: 1337 }) == 42 + assert' "rename" $ + get y (rename x y { x: 42 }) == 42 assert' "equal" $ equal { a: 1, b: "b", c: true } { a: 1, b: "b", c: true } assert' "equal2" $ @@ -42,10 +45,11 @@ main = do assert' "pokeSTRecord" $ stTest1.x == 42 && stTest1.y == "testing" - + let testBuilder = Builder.build (Builder.insert x 42 >>> Builder.merge { y: true, z: "testing" } - >>> Builder.delete y) {} + >>> Builder.delete y + >>> Builder.rename z y) {} assert' "Data.Record.Builder" $ - testBuilder.x == 42 && testBuilder.z == "testing" + testBuilder.x == 42 && testBuilder.y == "testing"