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

Added appendRecord, works on two records containing semigroups #5

Open
wants to merge 2 commits into
base: master
from

Conversation

Projects
None yet
5 participants
@AdamSaleh
Contributor

AdamSaleh commented Dec 2, 2017

Hi,

I created another one of these. Currently takes two records of the same row-type and creates a new record with the values appended with <>. Works on non-homogenous records, i.e:

appendRecord {a: "1", b: [2], c: "3"} {a: "a", b: [4], c: "c"} 

will result in

{a: "1a", b: [2,4], c: "3c"}

Do you think it would be a good idea to make this work for two records with different rows? I am not sure how would I do that yet :-)

@justinwoo

This comment has been minimized.

Owner

justinwoo commented Dec 2, 2017

Different rows? Like if there are keys missing from one or the other? Not sure if there's a easy way to do that without some kind of overloaded instances...

@AdamSaleh

This comment has been minimized.

Contributor

AdamSaleh commented Dec 7, 2017

@justinwoo so, after some help and discussion from @MonoidMusician and other people on #purescript channel on slack (mostly @paluh), I have decided to not do full outer-join append.

Instead, appendRow now supports the second row to be subset of the first, i.e:

appendRecord {a: "1", b: [2], c: "3", d:5} {a: "a", b: [4], c: "c"} 

will result in

{a: "1a", b: [2,4], c: "3c", d:5}

Then I have added memptyRecord that can initialize your record with empty values if all of the key-types are Monoids, i.e:

test "memptyRecord" do
  let emptyRecord = memptyRecord :: {a :: String, b :: Array Int}
  equal "" emptyRecord.a
  equal [] emptyRecord.b

What do you think?

My use-case probably would be in frontend architectures like purescript-pux, where you have a single state type and then you update it based on incoming events.
Now I could initialize the state by memptyRecord and update it with appendRecord :-)

@Gerstacker

This comment has been minimized.

Gerstacker commented Dec 7, 2017

I'm kind of working on an Entity Component System based on similar ideas. At initialization the user provides an RProxy specifying the set of all components that can be stored and the ECS creates an isomorphic record of, say, empty IntMap's, one for each component with the appropriate element type.

Then each entity will have associated with its ID only a subset of those components with values stored in the corresponding storages.

Each system consists of a function from one RProxy to another, with each of those containing only some subset of possible components. When the user wants to map a system over components, we collect the IntMaps associated with the rows present in the system's input Record type, intersect their key sets to know which entity ID's to operate on, and end up doing inserts into the storage rows specified in the system's output type.

I'm expecting to run into some showstopper along the way, but you're showing me this is closer to possible than I first thought. A stretch goal would be for the library to do automatic loop fusion, where if you're mapping two systems in sequence that use mostly the same rows, the ECS can "transpose" your strategy and do them both in one pass over the component storages. Having the whole structure represented at the type level means everything can be inlined, CSE's between the two systems can be optimized away, etc.

@AdamSaleh

This comment has been minimized.

Contributor

AdamSaleh commented Dec 7, 2017

@Gerstacker if I understand correctly, there should be lot of changes coming in this regard with the purescript 0.12 release, where we might start seeing some proper type-classes for records showing up.

I.e. maybe even proper Eq, e.t.c.

Currently there is little support for i.e. working with nested records with this stuff, but you could work around that easily by using new-types a.f.a.i.k.

On the other hand, what I couldn't really figure out is how to create a union with these, but it doesn't seem that you would need it.

It seems to be fairly simple to iterate over a single row-list and then build-up another 😄

You might want to check-out https://github.com/LiamGoodacre/purescript-record-apply as well, I thing there is a blog-post about it, might make more things easier?

In any-case, if I were you, I would try to make a prototype, and then complain on #purescript slack channel if it doesn't work. So far, somebody has always helped me with my weird questions 😃.

@Gerstacker

This comment has been minimized.

Gerstacker commented Dec 23, 2017

Hi @AdamSaleh, I hope it's ok to ask this here...
I've heard it said (purescript/purescript#2196) that "rows are not allowed in instance heads", but I see you are able to use () in, e.g. instance appendRecordNil. I have gotten that to work sometimes, but sometimes not. And I don't understand the difference.

@MonoidMusician

This comment has been minimized.

MonoidMusician commented Dec 23, 2017

Hi @Gerstacker, the rule for using () in instance heads is that you can use row literals like that when that parameter is fully determined by the other parameters in terms of functional dependencies on the class. So in MemptyRecord rl row it’s okay because the rowlist determines the row (rl -> row). This is to prevent you from actively matching on a row literal (I don’t know exactly why this is the case though).

This is documented somewhere, I don’t remember where off the top of my head.

@Gerstacker

This comment has been minimized.

Gerstacker commented Dec 23, 2017

Thanks @MonoidMusician, this does have something to do with it. I already had functional dependencies in both directions between this row type variable and a RowList type variable. What got rid of the "Type class instance head is invalid due to use of type" error was removing the FD from the row to the Row Type (keeping the one that determines the Row kind parameter as you suggest). There's now a compiler error elsewhere that I don't think is related.

@paluh

This comment has been minimized.

paluh commented Jan 8, 2018

Regarding mempty and append implementations (and maybe other typeclasses methods from this lib) - maybe it is worth to provide a newtype wrapper for record:

newtype Record' r = Record' (Record r)

to have Semigroup, Monoid etc. instances out of the box:

instance semigroupRecord' ∷ (RowToList r rl, AppendSubrecordImpl rl r r)  Semigroup (Record' r) where
  append (Record' r1) (Record' r2) = Record' $ appendRecord r1 r2
@Gerstacker

This comment has been minimized.

Gerstacker commented Mar 23, 2018

Hey @AdamSaleh and @MonoidMusician and @justinwoo
I just pushed https://github.com/Gerstacker/purescript-rowecs that implements some of my ideas for Row-type-based ECS in Purescript. Parts of it may have more general application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment