purescript / purescript-record Goto Github PK
View Code? Open in Web Editor NEWFunctions for working with records and polymorphic labels
License: BSD 3-Clause "New" or "Revised" License
Functions for working with records and polymorphic labels
License: BSD 3-Clause "New" or "Revised" License
I think it would be interesting to add these for Homogeneous
records, once we merge Type.Row.Homogeneous
.
Foreign.Object.ST and Data.Array.ST export means of creating empty mutable structures without having to thaw an immutable one. Should we also export new :: forall h. ST h (STRecord h ())
from Record.ST for consistency?
Thereโs also Foreign.Object.runST and Data.Array.ST.run to avoid copying the mutable structure in the end. Any reason not to add a similar run :: forall r. (forall h. ST h (STRecord h r)) -> Record r
to Record.ST? This would require an unsafeFreeze :: forall h r. STRecord h r -> ST h (Record r)
function that we could export with an unsafeThaw :: forall h r. Record r -> ST h (STRecord h r)
function from a new Record.ST.Unsafe module.
I've noticed that the merge
-like functions in 'Record' favor their first argument, whereas those in 'Record.Builder' favor their second. I'm guessing that a user would at least initially assume that Record merging and Builder merging would have identical semantics except for the function/Builder difference. Could this semantic distinction and its rationale be documented? Alternatively, could either Record merging or Builder merging be updated so that the two function sets are consistent with each other? (As far as I can tell, there's no pressing problem motivating this change, but consistency is often good for its own sake, and it might help to improve grokkability and to prevent merger-precedence bugs.)
-- Record
merge
:: forall r1 r2 r3 r4
. Union r1 r2 r3
=> Nub r3 r4
=> Record r1
-> Record r2
-> Record r4
merge l r = runFn2 unsafeUnionFn l r
-- Record.Builder
merge
:: forall r1 r2 r3 r4
. Row.Union r1 r2 r3
=> Row.Nub r3 r4
=> Record r2
-> Builder (Record r1) (Record r4)
merge r2 = Builder \r1 -> runFn2 unsafeUnionFn r1 r2
the modify
function adapted to ST Records
The docs don't say.
Like we have builders for arrays, we should have one for records. This would make it more efficient to build records in type class instances based on things like RowList
iteration.
Prototype: http://try.purescript.org/?backend=core&gist=27a137278caa203b3ebbd42f7386a0db
I'd like to investigate the use of linear types here as well, since I think they could improve things further.
like in purescript-arrays
This may be a case of serious user-error problem here, but it seems to me that Record.ST
is only useful in a very specific and probably not common situation: you can only use it to modify the values of existing fields in a record.
This isn't a complaint about thaw
being the only way to get an STRecord
, I see that there's an unreleased addition of a new
- but actually this is kinda useless, since you can't do anything with an STRecord h ()
aside from freeze
or run
. You cannot use the ST
interface to add or remove fields.
Given that it only allows modification, at the expense of one copy (at a minimum, two if freeze
is used rather than run
), it seems like this module may as well not exist - you can just modify an object with update syntax or by rebuilding it completely for the same cost.
I don't think it's fixable either - I think it could be done as an indexed monad perhaps, tracking the row in/out of each operation as the indexed part, but then it wouldn't be ST
anymore either.
If I'm wrong here, apologies, can someone show me how you'd use this to build a record from scratch. ๐
@MonoidMusician had a super-cool idea. A function which takes an n-field record as argument is like an n-arity function. If we can partially apply a function, we should be able to partially apply fields of a record.
It should work something like this:
-- If you have some function which takes a record as an argument...
f :: { field1 :: Int, field2 :: Int } -> Int
-- you could partially apply it.
curry f {field1: 1} :: { field2 :: Int } -> Int
Here's the snippet he pasted. He said it needs to be type-checked xD.
foreign import unsafeMergeImpl :: forall r1 r2 r.
Record r1 -> Record r2 -> Record r
unsafeMerge :: forall r1 r2 r. Union r1 r2 r =>
Record r1 -> Record r2 -> Record r
class CurryNamed
(expected :: # Type)
(given :: # Type)
(final :: Type)
(step :: Type)
| expected given final -> step
where
curryNamed ::
(Record expected -> final) ->
(Record given -> step)
instance curryNamed ::
( Union remaining given expected
, RowToList remaining remainingRL
, CurryNamedRL remainingRL expected remaining given final step
) => CurryNamed expected given final step
where
curryNamed = curryNamedRL (RLProxy :: RLProxy remainingRL)
class
( Union remaining given expected
, RowToList remaining remainingRL
) <= CurryNamedRL
(remainingRL :: RowList)
(expected :: # Type)
(remaining :: # Type)
(given :: # Type)
(final :: Type)
(step :: Type)
| remainingRL expected given final -> step remaining
where
curryNamedRL :: RLproxy remainingRL ->
(Record expected -> final) ->
(Record given -> step)
instance curryNamedNil ::
TypeEquals expected given =>
CurryNamedRL Nil expected () given final final
where
curryNamedRL _ = compose from
instance curryNamedCons ::
( ListToRow remainingRL remaining
, Union remaining given expected
, CurryNamed expected remaining final step
) => CurryNamedRL (Cons sym t rl) expected remaining given final step
where
curryNamedRL _ f g = curryNamed (f <<< flip unsafeMerge g)
If I understand right, using RowList its now possible to define Show and Eq instance for Record type. Is there any plans to add these instances? Without Show instance for Record types its hard to use the repl. (from)
At this moment this program is compiled with any issues
module Main where
import Data.Record.Builder as Builder
union :: forall r3 r1 r2. Union r1 r2 r3 => { | r1 } -> { | r2 } -> { | r3 }
union r1 r2 = Builder.build (Builder.merge r2) r1
y :: {a :: Int, a :: String }
y = {a: 1 } `union` {a: "foo" }
z :: {a :: Int, a :: Int }
z = {a: 1 } `union` {a: 1 }
Which I guess should be rejected.
changing type of union fixes the issue
import Type.Row (class RowListNub, class RowToList)
union
:: forall r1 r2 r3 r3l
. Union r1 r2 r3
=> RowToList r3 r3l
=> RowListNub r3l r3l
=> { | r1 }
-> { | r2 }
-> { | r3 }
union r1 r2 = Builder.build (Builder.merge r2) r1
I think adding similar constraints to Builder.merge will make it much safer.
If we add such constraints error is not that informative, this is what you would get for:
{a: 1 } `union` {a: "foo" }
error: Could not match type Nil with type Cons "a" String Nil
With special class (for example SafeMerge) + Fail
constraint the error could be made more useful.
The following works:
generate :: forall r. { | r} -> { x :: Int | r }
generate = ?a
But this doesn't:
data E (a :: # Type) = E
generate :: forall r. E { | r} -> E { x :: Int | r }
generate = ?a
Could not match kind
# Type
with kind
Type
What am I doing wrong? It seems like the kind of the parameter of E
is ignored and that the forall r
is defaulting to Type
instead of # Type
. However,
generate :: forall (r :: # Type). E { | r} -> E { x :: Int | r }
Mai... 59 20 error Err... Unable to parse module:
unexpected (
expecting identifier
kind signatures in a forall don't seem to be supported. What do I do?
Using "purescript": "^0.13.6"
and package set psc-0.13.6-20200423
Here's my foot gun:
main :: Effect Unit
main = do
Console.log "1"
let x = union {y: 5} {y: "5"}
Console.log "2"
let s = show x
Console.log "3"
Console.log s
Causes TypeError: s.replace is not a function
after logging 2
.
Changing to let x = union {y: "5"} {y: 5}
gets rid of error and produces expected representation: { y: "5", y: 5 }
Hi everyone! ๐
Would anyone be opposed to a build' :: Builder {} { | row } -> { | row }
function to build a record from scratch? The implementation is only flip build $ {}
but itโs a convenient little function to compose in some pointfree scenarios.
It would be nice to see Builder
be stack-safe, if possible. This might involve abandoning its current definition as a newtype
on a -> b
:
If i have forall in record then you can't insert
module Main where
import Prelude
import Control.Monad.Eff (Eff)
import Data.Record (insert)
import Data.Symbol (SProxy(..))
import Type.Row (class RowLacks)
type ConfigR r = (baz :: forall a. Array a | r)
type Config r = Record (ConfigR r)
add
:: forall r
. RowLacks "fiz" (ConfigR r)
=> Config r
-> Config ( fiz :: String | r)
add c = insert (SProxy :: SProxy "fiz") "foo" c
error is:
No type class instance was found for
Prim.Union ( baz :: forall a. Array a
| r4
)
( fiz :: Entry
)
( fiz :: t5
| t6
)
Any explanation of why it happens?
I guess if you have forall in record compiler can't calculate Union for it
Asked on slack, but figure it is less likely to get lost here.
I noticed there's no modify
for Data.Record.Builder
(akin to modify
in Data.Record
). Was that an intentional choice or an oversight? If an oversight, I'll submit a PR.
It might also be possible if there were a Functor
instance for Builder
. Would this work without breaking things? I remember we talked about adding a Profunctor
instance, but it would break something. So, Functor
is the next best thing, I think.
I have this stub, which provides some instances for homogeneous records:
https://github.com/paluh/purescript-homogeneous/blob/master/src/Data/Homogeneous/Record.purs#L30
Could you please tell me if you think that this could be a valuable addition to this library?
As this type is really isomorphic to sized vector I can provide more instances like Monad
. Currently I've stopped the implementation on the Applicative
.
Consider this:
F <$> x <*> y <*> z
This has a problem traditionally that you need to make sure you get the x/y/z order correct. If you're making forms (web forms) with e.g. a formlet library, this becomes pretty hard to manage.
What if we could do this?
partially F <$> pure {x:a} <*> pure {y:b} <*> pure {z:a}
(or alternatively by using SProxy :: SProxy "a"
, etc.)
So that partially F
would produce a function of one argument of one of the x, y or z fields (or more, I guess). When provided with one, it accepts another argument until all fields have been consumed.
I made special version of <*>
that achieves something similar:
module Record.Apply where
import Control.Applicative
import Prim.Row (class Nub, class Union)
import Record (disjointUnion)
-- API
applyFields
:: forall f inner outer combined.
Union inner outer combined
=> Nub combined combined
=> Apply f
=> f { | inner }
-> f { | outer }
-> f { | combined }
applyFields getInner getOuter =
disjointUnion <$> getInner <*> getOuter
infixl 5 applyFields as <|*>
-- Demo
newtype Foo = Foo { x :: Int, y :: String, z :: Array Int }
demo :: forall f. Applicative f => f Foo
demo = Foo <$> pure {y: ""} <|*> pure {x: 2} <|*> pure {z: []}
Which works nicely, but I think re-using <*>
would be cool too. Any ideas?
like in purescript-arrays
: purescript/purescript-arrays#129
It seems like a silly thing, but I have a need for this with simple-json usage. Should we add one to this library?
So far my implementation is as follows (from here):
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 value inter
where
value = get prev record
inter :: Record inter
inter = delete prev record
Add mergeFlipped
and an infix operator //
As a user I'm typically more thinking in terms of mergeFlipped
than merge
, as I want to overwrite a record with some new fields. E.g. in Typescript I can do
const newRecord = { ...otherRecord, more: 23 }
and in dhall //
is avaiable as a mergeFlipped
operator:
{ home = "/home/bill"
, privateKey = "/home/bill/.ssh/id_ed25519"
, publicKey = "/home/blil/.ssh/id_ed25519.pub"
} // { home = "/home/john" }
So in Purescript I would also like to do the following and I think it makes for a nice addition to purescript-record
:
person :: { age :: Int
, firstName :: Maybe String
, professional :: String
}
person =
{ firstName: "John", professional: true } //
{ professional: "Software Engineer", age: 58 } //
{ firstName: Just "Mike" }
I have created a PR to illustrate this #83
It seems fairly stable already.
Is there any interest in (or objection to) updating this library to use Visible Type Application syntax in place of Proxy
?
I know this is a somewhat dubious request :P but I'd like an unsafeHas
function. I would like to add something to purescript-variant
for partial matching with a record (right now it must always be total). It's possible for me to do this by contract
ing the variant first, which essentially does the "has" check for me, but it requires me to build a List of IsSymbol constraints on every invocation, and then walk the list to see if the variant tag is within bounds. However, if I have a record of case eliminators, then I could just use an unsafeHas
function to do the dynamic lookup instead, which wouldn't require any IsSymbol constraints.
now that we have this: purescript/purescript-typelevel-prelude@979f06a
It should be possible to do safe has
and get
operations on records with homogeneous rows.
There should be a Profunctor
instance for Builder
s.
Could be a handy utility function.
Can someone tell me why this simple function
singleton :: forall s a r. IsSymbol s => RowCons s a () r => SProxy s -> a -> Record r
singleton s a = insert s a {}
produces the following error, which seems like it's coming from the RowLacking
constraint that should be trivially satisfied
No type class instance was found for
Prim.RowCons s4
Entry
()
t5
The instance head contains unknown type variables. Consider adding a type annotation.
while applying a function insert
of type IsSymbol t0 => RowLacks t0 t1 => RowCons t0 t2 t1 t3 => SProxy t0 -> t2 -> { | t1 } -> { | t3 }
to argument s
while inferring the type of insert s
in value declaration singleton
where s4 is a rigid type variable
t2 is an unknown type
t0 is an unknown type
t3 is an unknown type
t1 is an unknown type
t5 is an unknown type
It would be nice to have a function that will drop/delete unnecessary record fields.
drop :: forall r1 r2. Record r1 -> Record r1
where r2 is a subset of r1
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.