pathom

:pathom: https://github.com/wilkerlucio/pathom/ & https://pathom3.wsscode.com & https://roamresearch.com/#/app/wsscode
nivekuil 2020-09-18T01:37:41.000100Z

can dynamic resolvers reduce the work done in a resolver? e.g. if you have a resolver with ::pc/output [:first-name :last-name] and you only query for [:first-name], can pathom modify the resolver logic so it doesn't query the db for :last-name too?

wilkerlucio 2020-09-18T01:45:12.001100Z

@kevin842 you mean a way to check in the resolver what query was done to him, to maybe avoid providing parts of it?

nivekuil 2020-09-18T01:45:21.001200Z

yeah

wilkerlucio 2020-09-18T01:45:49.001800Z

yes, that's something dynamic resolvers can do, and what you said is how I imagine the implementation of things like SQL drivers could be

wilkerlucio 2020-09-18T01:46:01.002200Z

the new planner gives to the dynamic resolver the exact query it should fulfil

wilkerlucio 2020-09-18T01:46:10.002400Z

(based on the user request)

nivekuil 2020-09-18T01:56:18.002500Z

sounds perfect :) I'm using pathom with crux and I guess that means there's no cost to defining a single resolver for a big document, so pathom can describe a de facto schema for crux with a 1:1 mapping between resolvers and documents

cjmurphy 2020-09-18T02:25:04.006800Z

Pathom 3 sounds great. You did say 'great time to bring up new ideas'. So the idea, which I know I've mentioned before, is for mutations to have inputs, so they work more like resolvers. The usual work around for this is to call the parser that's in the env again. Having inputs as 'first class' for mutations would make mutations look better, and surely improve other things too, like performance??

wilkerlucio 2020-09-18T04:41:36.006900Z

hello, I think this should be done in plugin lang, or using transforms, that's because adding the processing of parsing params as inputs can never be faster than just providing raw params, so I consider that mutations need to always have the most lightweight version possible available

wilkerlucio 2020-09-18T04:46:42.007200Z

but sure, I think providing those as built-ins could be a easy way to make it accessible, or maybe I'm missing something, what I said makes sense to you?

cjmurphy 2020-09-18T05:36:50.007500Z

Yes pretty much. A second arg to the mutation. Usually the mutation would have only one arg as now. So the performance stuff can perhaps be done automatically from what the user indicates (one or two args)...

henrik 2020-09-18T07:48:28.013100Z

Smart maps are intriguing. Seems a bit like the concept of a lazy seq, applied to a map.

eoliphant 2020-09-18T15:25:21.016600Z

+1 on the smart maps. I always thought Plumatic’s Graph was a super cool, but it doesn’t work with namespaced keywords, etc. seems a super cool fit with pathom

wilkerlucio 2020-09-18T15:33:59.017300Z

yup, I feel like you guys, and I also believe this is a much easier way to use pathom for newcomers, so they don't have to learn anything about EQL but can still leverage the resolvers engine

👍 1
2020-09-18T15:39:05.020800Z

Smart maps seem pretty neat, but I think I would prefer to use it via a different API from the normal clojure.core get. When I'm reading code and I see (:my.ns/foo m), I have an expectation that the operation will happen immediately. If (:my.ns/foo m) were to cause the parser to go out and make an API call to a microservice to get data (for example), I think that starts to tread on the principle of least surprise. Something like (pathom.smart-map/get m :my.ns/foo) is a bit more verbose, but also more explicit.

☝️ 2
2020-09-18T15:41:35.022300Z

Just 2 cents from someone with far less pathom experience. :)

wilkerlucio 2020-09-18T15:43:57.024900Z

@codonnell makes total sense, I think this decision between transparent or not can very depending on the use case. for example, I imagine smart maps can be used to make adapter layers, so you can swap names, in this case a user may prefer to have it transparent as a map, and don't care if its immediate or lazy. but if you are dealing with heavy things like API calls, and the surprise effect is problematic, then would be nice to make it explicit. and I think Smart Maps should support both cases, and this can be a matter of how to configure the smart map (to make lazy calls implicit or explicit)

💯 2
wilkerlucio 2020-09-18T15:46:05.027500Z

another interesting configuration that will happen is how the smart maps should respond to (keys smart-map), so far I imagine two options here: 1. cached - this is the current way, keys will respond with the keys that are cached on the map, so the lookup is constant and predictable 2. reachable - this will respond with all possible keys (considering the index and current context), interesting in some cases but needs to be used with care, because a (into {} smart-map) could trigger a lot of resolvers

2020-09-18T15:46:33.027800Z

Oh yeah, that is an interesting wrinkle.

2020-09-18T15:49:42.028800Z

2. could have interesting interactions with quite a bit of the clojure sequence API

2020-09-18T15:52:52.030900Z

If (keys smart-map) returned something different from the tuples in (seq smart-map), for example, (into {} smart-map) could return something different from (reduce (fn [m k] (assoc m k (get smart-map k))) {} (keys smart-map)), which would be very surprising.

2020-09-18T15:54:09.032400Z

Now that I'm thinking more about it, I can see how it would be valuable to treat smart maps like maps so you could use clojure.core collection functions on them.

2020-09-18T15:57:34.033700Z

Don't want to come off to negative; I am pretty excited to see pathom 3 as it develops. 🙂

1
henrik 2020-09-18T15:58:13.033900Z

That’s a solid point

henrik 2020-09-18T16:05:43.037100Z

Keeping in mind the goal of being approachable by new-comers, I think the path of least surprise would be the most sensible default, which to my mind would be get and keys behaving as you would expect them to behave when applied to a map. I.e., return the value, return all keys (does the map necessarily have to realise the values to return the complete set of keys?).

wilkerlucio 2020-09-18T16:11:08.038Z

@henrik I agree on the path of least surprise, to me so far that is having keys listing only cached, and with transparent reads

wilkerlucio 2020-09-18T16:11:27.038700Z

the keys thing doesn't need to realize the value, but gets dangerous when doing any scan operation (like (into {} smart-map))

henrik 2020-09-18T16:12:31.039900Z

I’d have to disagree with that, since it kind of becomes mutable (and therefore surprising). Reading the map changes the output of keys.

wilkerlucio 2020-09-18T16:13:16.040400Z

we could have different results from keys and seq, but I'm not sure that is least surprising, I guess we need to experiment more to figure those things out

henrik 2020-09-18T16:19:16.041500Z

Well, scan operations being potentially expensive isn’t unheard of in Clojure. Any lazy construct will have pitfalls like that.

wilkerlucio 2020-09-18T16:19:54.042200Z

yeah, but I fear that it could hurt programs in quite bad ways, therefore I rather have the default to be safer in this case

henrik 2020-09-18T16:24:05.047800Z

I think the “mutability” is the more dangerous problem in this case. We all have REPLs, and we can evaluate something and gauge the performance of it. But when you start talking about something that “changes in place”, like the output of keys following a get, you can easily get the wrong impressions of what’s happening just because you evaluated your forms in a particular order while developing (as in oops, you evaluated a get, the keys output is not going to look the same in production as it did in your REPL).

pithyless 2020-09-18T16:24:30.048100Z

What if the smart-map followed the conventions of Datomic Peer entities? keys would only return cached keys and you would have to do something akin to d/touch to realize everything.

wilkerlucio 2020-09-18T16:25:20.049200Z

@pithyless this is the direction I'm inclined to, sm also support (psm/load! smart-map eql-to-touch), which is similar the touch you are talking (but more specific, not getting everything, altough that could be an option)

pithyless 2020-09-18T16:27:35.050400Z

Yeah, that sounds great. I'm all in favor of being explicit via EQL ;]

pithyless 2020-09-18T16:29:40.052400Z

One could always support (psm/load! smart-map '[*]) if that was a common feature request.

👍 1
wilkerlucio 2020-09-18T16:30:04.052700Z

I can understand that, but I still more afraid of long runs, IME the graphs tend to get really big, and when they do, a single attribute may reach hundreds of resolvers, so even on the REPL a simple scan on a single attr could stop and break everything, this makes me feel this is a too dangerous operation to be the default, but as I said, from impressions so far, I'm down to change opinion depending on how the use cases unfold and we have a better understanding of the most common types of usage

henrik 2020-09-18T16:32:05.053600Z

In that case, maybe it would be better to stay away from core entirely, since it would kind of redefine what those functions do anyway, and expose a custom set of functions to interact with them.

henrik 2020-09-18T16:33:44.053800Z

With a disclaimer that I’m not at all sure, it’s certainly a tricky bunch of tradeoffs to juggle.

wilkerlucio 2020-09-18T16:33:44.054Z

will really depend on each use case, I really like the transparent idea for conversion layers, and users that think better defaults should be different, you can write your own smart map constructs to change the defaults in your application context, do you think that's a reasonable solution for difference in opinions around the defaults?

wilkerlucio 2020-09-18T16:34:18.054200Z

oh yeah, I agree its a tricky jungle, and I hope we can figure it out together what best practices around it may be (or even if its just a really bad ideia, haha)

henrik 2020-09-18T16:36:32.054400Z

If “build your own” means just seeding some kind of reusable construction function with a config that flips a few switches, maybe. Otherwise, people are probably going to use the defaults or avoid it entirely and go “classic”.

wilkerlucio 2020-09-18T16:37:47.054600Z

yeah, I mean writing something like this:

(defn my-smart-map [env context]
  (psm/smart-map 
    (merge {::psm/keys-mode ::psm/keys-mode-reachable} 
           env)
    context))

👍 1
henrik 2020-09-18T16:42:10.054900Z

If you expect it to be common enough, you could shave away the env, context with a helper maybe:

(def my-smart-map (psm/make-smart-map {::psm/keys-mode ::psm/keys-mode-reachable}))

wilkerlucio 2020-09-18T16:45:16.055200Z

we can use a partial in that:

(def my-smart-map (partial psm/smart-map {::psm/keys-mode ::psm/keys-mode-reachable}))
what makes hard to shave the env out is because you also need to provide indexes, if your app wants to use a single index for all smart maps, that's fine, but otherwise you will still want to have the env available to change (to use different indexes)

wilkerlucio 2020-09-18T16:46:25.055400Z

and maybe different indexes will be the key for different usages, a simple index with mostly aliases and quick operations should be safe to allow all keys, but one with many heavy resolvers will want a different setting

henrik 2020-09-18T16:57:40.056Z

(defn make-smart-map [config]
  (fn [env context]
    (psm/smart-map (merge config env) context)))
?

wilkerlucio 2020-09-18T17:04:00.056200Z

yeah 👍