Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use vta #86

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 35 additions & 41 deletions src/Record.purs
Original file line number Diff line number Diff line change
Expand Up @@ -31,76 +31,72 @@ import Unsafe.Coerce (unsafeCoerce)
-- | For example:
-- |
-- | ```purescript
-- | get (Proxy :: Proxy "x") :: forall r a. { x :: a | r } -> a
-- | get @"x" :: forall r a. { x :: a | r } -> a
-- | ```
get
:: forall r r' l a
:: forall r r' @l a
. IsSymbol l
=> Cons l a r' r
=> Proxy l
-> Record r
=> Record r
-> a
get l r = unsafeGet (reflectSymbol l) r
get r = unsafeGet (reflectSymbol (Proxy :: _ l)) r

-- | Set a property for a label which is specified using a value-level proxy for
-- | a type-level string.
-- |
-- | For example:
-- |
-- | ```purescript
-- | set (Proxy :: Proxy "x")
-- | set @"x"
-- | :: forall r a b. a -> { x :: b | r } -> { x :: a | r }
-- | ```
set
:: forall r1 r2 r l a b
:: forall r1 r2 r @l a b
. IsSymbol l
=> Cons l a r r1
=> Cons l b r r2
=> Proxy l
-> b
=> b
-> Record r1
-> Record r2
set l b r = unsafeSet (reflectSymbol l) b r
set b r = unsafeSet (reflectSymbol (Proxy :: _ l)) b r

-- | Modify a property for a label which is specified using a value-level proxy for
-- | a type-level string.
-- |
-- | For example:
-- |
-- | ```purescript
-- | modify (Proxy :: Proxy "x")
-- | modify @"x"
-- | :: forall r a b. (a -> b) -> { x :: a | r } -> { x :: b | r }
-- | ```
modify
:: forall r1 r2 r l a b
:: forall r1 r2 r @l a b
. IsSymbol l
=> Cons l a r r1
=> Cons l b r r2
=> Proxy l
-> (a -> b)
=> (a -> b)
-> Record r1
-> Record r2
modify l f r = set l (f (get l r)) r
modify f r = set @l (f (get @l r)) r

-- | Insert a new property for a label which is specified using a value-level proxy for
-- | a type-level string.
-- |
-- | For example:
-- |
-- | ```purescript
-- | insert (Proxy :: Proxy "x")
-- | insert @"x"
-- | :: forall r a. Lacks "x" r => a -> { | r } -> { x :: a | r }
-- | ```
insert
:: forall r1 r2 l a
:: forall r1 r2 @l a
. IsSymbol l
=> Lacks l r1
=> Cons l a r1 r2
=> Proxy l
-> a
=> a
-> Record r1
-> Record r2
insert l a r = unsafeSet (reflectSymbol l) a r
insert a r = unsafeSet (reflectSymbol (Proxy :: _ l)) a r

-- | Delete a property for a label which is specified using a value-level proxy for
-- | a type-level string.
Expand All @@ -111,18 +107,17 @@ insert l a r = unsafeSet (reflectSymbol l) a r
-- | For example:
-- |
-- | ```purescript
-- | delete (Proxy :: Proxy "x")
-- | delete @"x"
-- | :: forall r a. Lacks "x" r => { x :: a | r } -> { | r }
-- | ```
delete
:: forall r1 r2 l a
:: forall r1 r2 @l a
. IsSymbol l
=> Lacks l r1
=> Cons l a r1 r2
=> Proxy l
-> Record r2
=> Record r2
-> Record r1
delete l r = unsafeDelete (reflectSymbol l) r
delete r = unsafeDelete (reflectSymbol (Proxy :: _ l)) r

-- | Rename a property for a label which is specified using a value-level proxy for
-- | a type-level string.
Expand All @@ -133,22 +128,21 @@ delete l r = unsafeDelete (reflectSymbol l) r
-- | For example:
-- |
-- | ```purescript
-- | rename (Proxy :: Proxy "x") (Proxy :: Proxy "y")
-- | rename @"x" @"y"
-- | :: forall a r. Lacks "x" r => Lacks "y" r => { x :: a | r} -> { y :: a | r}
-- | ```
rename :: forall prev next ty input inter output
rename
:: forall @prev @next ty input inter output
. IsSymbol prev
=> IsSymbol next
=> Cons prev ty inter input
=> Lacks prev inter
=> Cons next ty inter output
=> Lacks next inter
=> Proxy prev
-> Proxy next
-> Record input
=> Record input
-> Record output
rename prev next record =
insert next (get prev record) (delete prev record :: Record inter)
rename record =
insert @next (get @prev record) (delete @prev record :: Record inter)

-- | Merges two records with the first record's labels taking precedence in the
-- | case of overlaps.
Expand Down Expand Up @@ -221,22 +215,22 @@ equal
=> Record r
-> Record r
-> Boolean
equal a b = equalFields (Proxy :: Proxy rs) a b
equal a b = equalFields @rs a b

class EqualFields (rs :: RowList Type) (row :: Row Type) | rs -> row where
equalFields :: Proxy rs -> Record row -> Record row -> Boolean
equalFields :: Record row -> Record row -> Boolean

instance equalFieldsCons
::
instance equalFieldsCons ::
( IsSymbol name
, Eq ty
, Cons name ty tailRow row
, EqualFields tail row
) => EqualFields (Cons name ty tail) row where
equalFields _ a b = get' a == get' b && equalRest a b
) =>
EqualFields (Cons name ty tail) row where
equalFields a b = get' a == get' b && equalRest a b
where
get' = get (Proxy :: Proxy name)
equalRest = equalFields (Proxy :: Proxy tail)
get' = get @name
equalRest = equalFields @tail

instance equalFieldsNil :: EqualFields Nil row where
equalFields _ _ _ = true
equalFields _ _ = true
40 changes: 18 additions & 22 deletions src/Record/Builder.purs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import Data.Function.Uncurried (runFn2)
import Data.Symbol (class IsSymbol, reflectSymbol)
import Prim.Row as Row
import Record.Unsafe.Union (unsafeUnionFn)
import Type.Proxy (Proxy)
import Type.Proxy (Proxy(..))
import Unsafe.Coerce (unsafeCoerce)

foreign import copyRecord :: forall r1. Record r1 -> Record r1
Expand All @@ -41,12 +41,12 @@ foreign import unsafeRename :: forall r1 r2. String -> String -> Record r1 -> Re
-- | For example:
-- |
-- | ```purescript
-- | build (insert x 42 >>> insert y "testing") {} :: { x :: Int, y :: String }
-- | build (insert @"x" 42 >>> insert @"y" "testing") {} :: { x :: Int, y :: String }
-- | ```
newtype Builder a b = Builder (a -> b)

-- | Build a record, starting from some other record.
build :: forall r1 r2. Builder (Record r1) (Record r2) -> Record r1 -> Record r2
build :: forall @r1 r2. Builder (Record r1) (Record r2) -> Record r1 -> Record r2
build (Builder b) r1 = b (copyRecord r1)

-- | Build a record from scratch.
Expand All @@ -62,48 +62,44 @@ derive newtype instance categoryBuilder :: Category Builder

-- | Build by inserting a new field.
insert
:: forall l a r1 r2
:: forall @l a r1 r2
. Row.Cons l a r1 r2
=> Row.Lacks l r1
=> IsSymbol l
=> Proxy l
-> a
=> a
-> Builder (Record r1) (Record r2)
insert l a = Builder \r1 -> unsafeInsert (reflectSymbol l) a r1
insert a = Builder \r1 -> unsafeInsert (reflectSymbol (Proxy :: _ l)) a r1

-- | Build by modifying an existing field.
modify
:: forall l a b r r1 r2
:: forall @l a b r r1 r2
. Row.Cons l a r r1
=> Row.Cons l b r r2
=> IsSymbol l
=> Proxy l
-> (a -> b)
=> (a -> b)
-> Builder (Record r1) (Record r2)
modify l f = Builder \r1 -> unsafeModify (reflectSymbol l) f r1
modify f = Builder \r1 -> unsafeModify (reflectSymbol (Proxy :: _ l)) f r1

-- | Build by deleting an existing field.
delete
:: forall l a r1 r2
:: forall @l a r1 r2
. IsSymbol l
=> Row.Lacks l r1
=> Row.Cons l a r1 r2
=> Proxy l
-> Builder (Record r2) (Record r1)
delete l = Builder \r2 -> unsafeDelete (reflectSymbol l) r2
=> Row.Lacks l r1
=> Row.Cons l a r1 r2
=> Builder (Record r2) (Record r1)
delete = Builder \r2 -> unsafeDelete (reflectSymbol (Proxy :: _ l)) r2

-- | Build by renaming an existing field.
rename :: forall l1 l2 a r1 r2 r3
rename
:: forall @l1 @l2 a r1 r2 r3
. IsSymbol l1
=> IsSymbol l2
=> Row.Cons l1 a r2 r1
=> Row.Lacks l1 r2
=> Row.Cons l2 a r2 r3
=> Row.Lacks l2 r2
=> Proxy l1
-> Proxy l2
-> Builder (Record r1) (Record r3)
rename l1 l2 = Builder \r1 -> unsafeRename (reflectSymbol l1) (reflectSymbol l2) r1
=> Builder (Record r1) (Record r3)
rename = Builder \r1 -> unsafeRename (reflectSymbol (Proxy :: _ l1)) (reflectSymbol (Proxy :: _ l2)) r1

-- | Build by merging existing fields from another record, taking precedence
-- | in the case of overlaps.
Expand Down
6 changes: 3 additions & 3 deletions test/Examples.purs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ y_ = Proxy :: Proxy "y"
z_ = Proxy :: Proxy "z"

gotX :: Int
gotX = Record.get x_ { x: 1 }
gotX = Record.get @"x" { x: 1 }

insertedX :: { x :: Int }
insertedX = Record.insert x_ 1 {}
Expand All @@ -19,10 +19,10 @@ deletedX :: {}
deletedX = Record.delete x_ { x: 1 }

setX1 :: { x :: Int }
setX1 = Record.set x_ 1 { x: 0 }
setX1 = Record.set @"x" 1 { x: 0 }

setX2 :: { x :: Unit }
setX2 = Record.set x_ unit { x: 0 }
setX2 = Record.set @"x" unit { x: 0 }

modifyX :: { x :: Int }
modifyX = Record.modify x_ (\value -> value + 1) { x: 0 }
Expand Down
70 changes: 42 additions & 28 deletions test/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -7,62 +7,76 @@ import Record (delete, equal, get, insert, merge, modify, rename, set)
import Record.Builder as Builder
import Record.Unsafe (unsafeHas)
import Test.Assert (assert')
import Type.Proxy (Proxy(..))

main :: Effect Unit
main = do
let x = Proxy :: Proxy "x"
y = Proxy :: Proxy "y"
z = Proxy :: Proxy "z"

assert' "insert, get" $
get x (insert x 42 {}) == 42
get @"x" (insert @"x" 42 {}) == 42
assert' "insert, modify, get" $
get x (modify x (_ + 1) (insert x 42 {})) == 43
get @"x" (modify @"x" (_ + 1) (insert @"x" 42 {})) == 43
assert' "set, get" $
get x (set x 0 { x: 42 }) == 0
get @"x" (set @"x" 0 { x: 42 }) == 0
assert' "set, modify, get" $
get x (modify x (_ + 1) (set x 0 { x: 42 })) == 1
get @"x" (modify @"x" (_ + 1) (set @"x" 0 { x: 42 })) == 1
assert' "delete, get" $
get x (delete y { x: 42, y: 1337 }) == 42
get @"x" (delete @"y" { x: 42, y: 1337 }) == 42
assert' "rename" $
get y (rename x y { x: 42 }) == 42
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" $
not $ equal { a: 1, b: "b", c: true } { a: 1, b: "b", c: false }
assert' "equal2"
$ not
$ equal { a: 1, b: "b", c: true } { a: 1, b: "b", c: false }
assert' "merge" $
equal { x: 1, y: "y" } (merge { y: "y" } { x: 1, y: 2 })
assert' "unsafeHas1" $
unsafeHas "a" { a: 42 }
assert' "unsafeHas2" $
not $ unsafeHas "b" { a: 42 }
assert' "unsafeHas2"
$ not
$ unsafeHas "b" { a: 42 }

let testBuilder = Builder.build (Builder.insert x 42
>>> Builder.merge { y: true, z: "testing" }
>>> Builder.delete y
>>> Builder.modify x show
>>> Builder.rename z y) {}
let
testBuilder =
Builder.build
( Builder.insert @"x" 42
>>> Builder.merge { y: true, z: "testing" }
>>> Builder.delete @"y"
>>> Builder.modify @"x" show
>>> Builder.rename @"z" @"y"
)
{}

assert' "Record.Builder" $
testBuilder.x == "42" && testBuilder.y == "testing"

assert' "Record.Builder.merge" $
let { x, y, z } = Builder.build (Builder.merge { x: 1, y: "y" }) { y: 2, z: true }
let
{ x, y, z } =
Builder.build (Builder.merge { x: 1, y: "y" }) { y: 2, z: true }
:: { x :: Int, y :: String, z :: Boolean }
in x == 1 && y == "y" && z
in
x == 1 && y == "y" && z

assert' "Record.Builder.union" $
let { x, y, z } = Builder.build (Builder.union { x: 1, y: "y" }) { y: 2, z: true }
let
{ x, y, z } =
Builder.build (Builder.union { x: 1, y: "y" }) { y: 2, z: true }
:: { x :: Int, y :: String, y :: Int, z :: Boolean }
in x == 1 && y == "y" && z
in
x == 1 && y == "y" && z

assert' "Record.Builder.flip merge" $
let { x, y, z } = Builder.build (Builder.flip Builder.merge { x: 1, y: "y" }) { y: 2, z: true }
let
{ x, y, z } =
Builder.build (Builder.flip Builder.merge { x: 1, y: "y" }) { y: 2, z: true }
:: { x :: Int, y :: Int, z :: Boolean }
in x == 1 && y == 2 && z
in
x == 1 && y == 2 && z

assert' "Record.Builder.flip union" $
let { x, y, z } = Builder.build (Builder.flip Builder.union { x: 1, y: "y" }) { y: 2, z: true }
let
{ x, y, z } =
Builder.build (Builder.flip Builder.union { x: 1, y: "y" }) { y: 2, z: true }
:: { x :: Int, y :: Int, y :: String, z :: Boolean }
in x == 1 && y == 2 && z
in
x == 1 && y == 2 && z