Skip to content
This repository was archived by the owner on Mar 19, 2024. It is now read-only.

Added appendRecord, works on two records containing semigroups #5

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

AdamSaleh
Copy link
Contributor

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
Copy link
Owner

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
Copy link
Contributor Author

@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
Copy link

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
Copy link
Contributor Author

@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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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 subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants