asami

cjsauer 2021-10-22T16:36:36.001600Z

Hey there 👋 I’m scanning through some asami code and I noticed that the d/entity function has an arity-3 variant that includes a nested? boolean. What does this do exactly? I’m experimenting with setting it to false in an attempt to achieve a bit of laziness while crawling the graph, but it still seems to auto-pull nested entities.

quoll 2021-10-22T16:37:13.002Z

Hmmm, I thought it wasn’t supposed to when set to false? I haven’t looked at it in a long while though!

quoll 2021-10-22T16:40:13.002300Z

Ah, I see. It’s not general nested entities

cjsauer 2021-10-22T16:45:01.005500Z

Seems like the key lies on these lines here: https://github.com/threatgrid/asami/blob/40e8cac1eb8dd87ddf1e975455da27b9d688ab6b/src/asami/entities/reader.cljc#L82-L83

cjsauer 2021-10-22T16:45:50.006100Z

I wonder if my nested entities do not possess the :tg/entity key?

quoll 2021-10-22T16:46:08.006500Z

it covers the case of things like:

[{:db/ident "first"
  :val 1}
 {:db/ident "second"
  :val 2
  :inner {:db/ident "first"}}]
This has 2 “top-level” individually addressable entities. If you call: (entity db "second") you’ll get: {:val 2 :inner {:db/ident "first"}} But if you use: (entity db "second" true) you’ll get: {:val 2 :inner {:val 1}}

cjsauer 2021-10-22T16:46:30.006900Z

Ohh okay I see

quoll 2021-10-22T16:47:20.007900Z

If it finds a “top-level” entity, then it just includes a reference to the entity (e.g. {:db/ident "first"}) But if you say, “No, I really do want you to go in and retrieve this other thing” then it will

cjsauer 2021-10-22T16:48:23.008800Z

I see. So maybe this is a side effect of how I’m transacting my data. My use case is language AST analysis, so I’m directly transacting the AST straight into asami. I don’t think I have any true “top-level” entities in this case…

quoll 2021-10-22T16:49:26.009600Z

Are you transacting triples, or are you transacting a sequence of maps?

cjsauer 2021-10-22T16:49:52.010100Z

It’s a sequence of a single map, which is very deeply nested. I’m basically transacting one big tree.

quoll 2021-10-22T16:50:13.010600Z

OK. Only the very top object will be considered “top level” then

quoll 2021-10-22T16:51:16.011500Z

You could always add on the attribute of {:tg/entity true} if you want to tell Asami to treat the entity as a top-level thing

quoll 2021-10-22T16:52:14.012200Z

“top level” was an idea that was just created to deal with ingesting and exporting JSON

cjsauer 2021-10-22T16:53:41.013300Z

Ah nice, I was just going to ask whether I should normalize my data manually ahead of time. I should be able to walk this tree and assoc :tg/entity easily.

cjsauer 2021-10-22T16:54:23.014100Z

Out of curiosity, are there ways to “walk” the graph other than with d/entity? Maybe something lower level, like with d/graph which I admittedly haven’t looked far into yet.

cjsauer 2021-10-22T16:55:00.015200Z

I’m shooting for something lazy. I saw your comment in the docs that hinted at d/entity being lazy one day.

quoll 2021-10-22T16:55:16.015700Z

The entity function really does just walk the graph 🙂

cjsauer 2021-10-22T16:55:29.016100Z

Haha ok cool, then I’m in the right place 🙂

quoll 2021-10-22T16:55:35.016300Z

“One day” yes

quoll 2021-10-22T16:55:45.016600Z

For now… notsomuch

cjsauer 2021-10-22T16:56:34.017400Z

I’m wondering if I could contrib something here, but I think my use-case is too narrow. I know asami supports durable storage now too, so that might complicate things considerably in the lazy department…

quoll 2021-10-22T16:57:33.018700Z

Not really. At least, not with the current architecture

cjsauer 2021-10-22T16:57:48.019100Z

Naively I want to slap a lazy-seq in there and call it a day, but surely that’s overly simplistic?

quoll 2021-10-22T17:00:08.020200Z

Well, the entities are based on key-value pairs that are lazy. Right now, they get turned into an entity via (into {} …)

quoll 2021-10-22T17:00:31.020600Z

But that only matters if you want to iterate over keys

quoll 2021-10-22T17:02:26.022200Z

what it needs is a lazy-seq type of approach where you have an object that meets the clojure.lang.Associative interface, and holds onto a map of what has been read so far, plus the context to load up anything that’s missing

cjsauer 2021-10-22T17:03:31.022700Z

Yea, sounds a lot like Wilker’s https://github.com/wilkerlucio/pathom3/blob/master/src/main/com/wsscode/pathom3/interface/smart_map.cljc

quoll 2021-10-22T17:04:05.023100Z

This is basically what Datomic entities do

cjsauer 2021-10-22T17:04:54.023900Z

That “lazy map” would be awesome to have as its own library even. I wonder if there is prior art in generalizing it.

cjsauer 2021-10-22T17:07:40.024900Z

To generalize it, I imagine you’d need to have the user pass in their own lazy-seq’d thunk that produces the next kv pair? Hm…that wouldn’t really work though…it’d be O(N) on key access…

cjsauer 2021-10-22T17:08:38.025300Z

Both Datomic and Pathom have the advantage of a schema-like hint data structure.

quoll 2021-10-22T17:10:23.026400Z

I think it needs an API for looking up keys to get a value. So basically a cached map

cjsauer 2021-10-22T17:12:34.027Z

Yea you’re right. Datascript uses a (volatile! {}) as the internal cache

cjsauer 2021-10-22T17:13:24.027800Z

Even datascript has a schema upfront for things like refs…asami’s would need to be more dynamic

quoll 2021-10-22T17:13:55.028400Z

That would make me nervous on durable storage. I think I’d prefer an atom

cjsauer 2021-10-22T17:17:43.029400Z

Does asami support any special keywords on d/entity maps? For example, datascript lets you walk backwards using reverse refs with an underscore: :person/_friends

quoll 2021-10-22T17:18:39.029600Z

No, but I need to

quoll 2021-10-22T17:19:29.030300Z

However, I think I can only do that if I do the cached-map-plus-query-context objects

cjsauer 2021-10-22T17:20:13.031100Z

Yea I think so too. As it stands I don’t see how it could happen. Are you picturing a separate API for this, like d/lazy-entity kind of thing?

quoll 2021-10-22T17:23:54.031300Z

nope 🙂

quoll 2021-10-22T17:24:25.031800Z

I’ll just make it another instance of Associative/IPersistentMap

cjsauer 2021-10-22T17:25:32.032700Z

That makes sense. For some reason I thought it would change existing behavior, but if it does it means the implementation is wrong 🙂

cjsauer 2021-10-22T17:27:46.033700Z

I might take a crack at this and if I come up with something promising I will definitely share. It sounds like an interesting puzzle.

quoll 2021-10-22T17:47:52.034500Z

I think it should be relatively tractable. Reading an entity would return an object that is both an atom (so it can be shared) and a db (which is an immutable read-only structure). Getting any value by key is just a lookup in the map, and if it’s not there, then do a lookup in the graph associated with the db. There are 3 cases here: • If it’s a simple value, then just associate the key with the value in the cache, and return the value. • If it’s a sequential type (these are stored as linked lists), then I think it’s appropriate to get all of these eagerly. • If it’s an object type, then create a new cached entity, just like this one, with the same db, and a new root ID to build on

cjsauer 2021-10-22T17:49:43.034700Z

Cool, I think we’re on the same page. What is your reasoning behind eagerly fetching sequences? Not disagreeing at all, just curious. Does that have to do with durability?

quoll 2021-10-22T17:56:48.034900Z

Not at all. It just didn’t seem to be worth the hassle of laziness. But it would be so easy to wrap it in a lazy-seq that perhaps it’s no big deal

cjsauer 2021-10-22T17:59:26.035500Z

Can do eager at first and then reach for lazy-seq after maybe. That would keep the first draft focused on one thing.

quoll 2021-10-22T17:59:40.035700Z

I’m thinking that it would be nice to have a LazySeq that let you provide a count, because that can be returned immediately without having to traverse the linked list 🤔

quoll 2021-10-22T18:00:00.035900Z

But, yes. I think eager is fine

cjsauer 2021-10-22T17:28:14.034300Z

Thanks a lot for your help on the :tg/entity tag, I will try that. Asami is awesome btw. Your code is a joy to read 🙂

đź’– 1