Fork me on GitHub
#pathom
<
2022-08-02
>
markaddleman02:08:42

I'd like the input maps to resolvers be smart maps so I can pass the input to internal functions which can extract any data they need out of the environment and other resolvers. I wrote the following plugin:

(p.plugin/defplugin smart-map-plugin
  {::pcr/wrap-resolve (fn [resolver]
                        (fn [env input]
                          (let [smart-input (if (psm/smart-map? input)
                                              input
                                              (psm/smart-map (-> env
                                                                 (pcp/reset-env)
                                                                 (psm/with-error-mode ::psm/error-mode-loud))
                                                input))]
                            (resolver env smart-input))))})
Probably not surprisingly, this results in a stack overflow when I try some basic EQL queries. What should this plugin do in order for the wrapped resolver to receive a smart map as input?

Ben Grabow17:08:57

Instead of resolvers pulling their dependent data out of a smart map, why not declare the dependent attributes in the arglist of the resolver?

markaddleman17:08:38

Locality and convenience. That approach would require updating all resolvers using a function when that function's parameters change

Ben Grabow17:08:03

I don't quite follow. Do you have a concrete example that we could refer to?

Ben Grabow17:08:24

I'm looking at your other message here and it looks like there's a dependency cycle implied here:

(pco/defresolver id->user [{id :acme.user/id :as input}]
  (println (:acme.user/birth-year input))
  {:acme.user/name "Mark"})
(pco/defresolver name->birth-year [{name :acme.user/name}]
  {:acme.user/birth-year 1970})
:acme.user/name explicitly depends on :acme.user/id and implicitly depends on :acme.user/birth-year, but :acme.user/birth-year explicitly depends on acme.user/name. So if we have an id we can't resolve either a name or birth-year because there's no path to get one without the other.

markaddleman18:08:21

Good point. Does it surprise you that it completes at all?

markaddleman18:08:35

> I don’t quite follow. Do you have a concrete example that we could refer to? I don’t have a concrete example at hand. Suppose I have two resolvers, R1 and R2. These two resolvers call function F which takes a kw-args map as a parameter. In order to make this code function, I must duplicate the keywords from F’s parameter into the inputs of R1 and R2. If F’s parameters change, then I must update both R1 and R2. However, if I can pass a smart map into F and all of F’s parameters can be computed from the environment, then there is no code duplication and I don’t have to remember to update R1 and R2 when F changes.

Ben Grabow18:08:30

Why not make F a resolver also?

Ben Grabow18:08:21

If Pathom is a system for managing function dependencies, and F has a new dependency, then I think it makes sense to expose those dependencies to Pathom for Pathom to manage them instead.

markaddleman18:08:43

Currently, my code is structured like that: Lots of internal functions are resolvers. This approach definitely works but introduces unnecessary abstractions. Under this approach, F needs a name and an output key. The output key is superfluous and my meager brain gets overloaded.

Ben Grabow18:08:48

R1 and R2 instead of calling F directly, they can put F's return attribute in their input signature.

markaddleman18:08:10

In addition, there are plenty of times when F only called after some condition is true. If F is a resolver, then F can be executed unnecessarily .

markaddleman13:08:06

This little sample program works. The execution is a little surprising. There are many printlns when I would have expected a stack overflow