off-topic

https://github.com/clojurians/community-development/blob/master/Code-of-Conduct.md Clojurians Slack Community Code of Conduct. Searchable message archives are at https://clojurians-log.clojureverse.org/
2020-08-29T17:38:01.184400Z

I came to the weird realization that GraphQL does not collect a graph of data. It is only collecting a tree. It is not a query language to gather graphs.

phronmophobic 2020-08-29T17:39:38.184500Z

I thought trees were technically graphs. even though it doesn't collect a graph, doesn't it search a graph?

p-himik 2020-08-29T17:42:32.184700Z

By itself, it doesn't search anything - it's just a query language. How the actual implementation is done is, well, an implementation detail.

2020-08-29T17:42:50.184900Z

GraphQL was made for decorating html trees, that’s why it returns a tree.

p-himik 2020-08-29T17:43:58.185100Z

Do you have a source that confirms the first part of that sentence? Because that by itself sounds unlikely.

2020-08-29T17:44:35.185300Z

well .. graphs can be implemented as trees, but I recently met a situation where I really need to represent a graph of values, and GraphQL and EQL can’t do that.

p-himik 2020-08-29T17:45:22.185500Z

Hold on. You need to represent a graph. But you're looking for a query language. I don't see how the two are connected.

phronmophobic 2020-08-29T17:45:24.185700Z

you can represent a graph of values, but some of the nodes would need to represent references to other parts of the tree

2020-08-29T17:45:58.186Z

I have no source, just my impression that GraphQL was made for improving data loading at the time where everybody was doing React.

2020-08-29T17:47:10.186200Z

@p-himik I encountered a situation where I need to express a query in the shape of a graph instead of a tree.

p-himik 2020-08-29T17:48:10.186400Z

This is a graph with a single cycle represented as an EDN structure with an ID convention on top of it:

[{:id 1, :sister-id 2}
 {:id 2, :brother-id 1}]
Any query language that I have heard of can return similar data. Or query based on similar data. @vincent.cantin Can you give an actual example?

➕ 1
2020-08-29T17:49:58.188200Z

Yes. Imagine that we are developing a re-frame app. There is a directed graph of values being computed in the subscriptions. Now, imagine that we want to tell the server about this graph, so that it plays a role to update its values.

p-himik 2020-08-29T17:51:06.189Z

> There is a directed graph of value being computed in the subscriptions I do not understand this sentence. Can you provide some [pseudo]code?

2020-08-29T17:51:56.189300Z

I don’t have code for my example.

2020-08-29T17:53:15.189500Z

(let [c (+ a b)
      d (* a b)
      e (- a d c)])

p-himik 2020-08-29T17:53:20.189700Z

Consider this:

(reg-sub :x
  ...)

(reg-sub :a
  :<- [:x]
  ...)

(reg-sub :b
  :<- [:x]
  ...)

(reg-sub :c
  :<- [:a]
  :<- [:b]
  ...)
Is this what you mean by "a directed graph of value being computed"?

2020-08-29T17:53:34.189900Z

Let’s imagine that this is the graph I am talking about.

2020-08-29T17:53:49.190100Z

Yes, that’s what I mean.

p-himik 2020-08-29T17:53:51.190300Z

OK. So what is the problem?

phronmophobic 2020-08-29T17:55:02.190500Z

any graph can be represented by a list of nodes and a list of edges. a list of nodes and edges can be represented by a tree:

{:nodes #{:a :b :c}
 :edges #{ [:a :b] [:b :a] [:a :a] ....} }

2020-08-29T17:56:09.190700Z

The problem is: if I want to describe the usage of the subscriptions by the views, I cannot do it with a tree. I cannot create a EQL structure and tell the server “this is what I am currently using”, because some nodes will be duplicated while in reality I reuse those nodes (that’s the difference between a tree and a directed graph: some leaves can be joined into the same nodes).

2020-08-29T17:56:57.190900Z

So, as @smith.adriane said, I will have to transform the graph and use a custom format. Not EQL or GraphQL.

p-himik 2020-08-29T17:57:16.191100Z

And when it's a bit unwieldy, you can just encode edges within the nodes themselves:

[{:id :x}
 {:id :a, :dependencies [:x]}
 {:id :b, :dependencies [:x]}
 {:id :c, :dependencies [:a, :b]}]

2020-08-29T17:57:34.191300Z

I am not complaining, I am just sharing the anecdote .. because the name is “GraphQL”

phronmophobic 2020-08-29T17:58:24.191500Z

my point is that you could construct graphql query that represents a graph

p-himik 2020-08-29T17:58:36.191700Z

@vincent.cantin Hold on, again. :) You want to "tell a server" about something. Why do you want to use a query language for that? But even with a query language, it's still possible, although it wouldn't make much sense IMO. Every map in my vector above can be represented with EQL.

➕ 1
p-himik 2020-08-29T18:04:58.192Z

FWIW, if I had to deal with the scenario above, I would just create a POST request (or a WebSocket message) and feed it with the relevant EDN data, that's it. Regular HTTP API. Not REST, not GraphQL, not EQL, not whatever else - just plain old HTTP API. If I really had to use e.g. EQL for that, I would use mutations: https://github.com/edn-query-language/eql#28-mutations And it would work in exactly the same way as with the regular HTTP API, only the actual data would be wrapped in some fancy EQL just to make the query parser happy. Same works for GraphQL.

2020-08-29T18:06:24.192500Z

I am brainstorming on the design of a very different web framework, and exploring ideas. Some of what I say may not make sense - that’s normal.

👍 1
lilactown 2020-08-29T18:24:12.193500Z

GraphQL represents a graph query that returns a tree

👍 1
☝️ 1
lilactown 2020-08-29T18:26:11.193900Z

so in that regard, the underlying data source can form a graph, and graphql can easily query that graph. but the return value is a static tree, because that’s what we can serialize and send across a wire

lilactown 2020-08-29T18:30:56.194100Z

ex.:

query {
  user(id: 1) {
    name
    friends(id: 2) {
      name
      friends(id: 1) {
        name
      }
    }
  }
}
this data we’re querying is recursive (friends of user 1 are also friends of user 2), so we could keep going and infinitely nest this as many layers deep as we want. however, we almost always want to bottom out somewhere. computers are not great at representing infinite graphs, and we can’t serialize recursive data structures easily. so the query language describes the tree we want to get from the graph we’re querying

phronmophobic 2020-08-29T18:45:20.194300Z

there's a recipe for serializing recursive data structures, this isn't graphql, but the same idea applies: https://netflix.github.io/falcor/documentation/jsongraph.html#reference

phronmophobic 2020-08-29T18:45:28.194600Z

> Using References, it is possible to model a graph in JSON.

lilactown 2020-08-29T18:46:38.194900Z

yep, falcor does have a way of serializing it but then you need an interpreter on the frontend to realize the graph. that’s a tradeoff.

lilactown 2020-08-29T18:47:24.195100Z

using an actual graph data structure on the frontend also has some tradeoffs, like the inability to console.log it effectively

lilactown 2020-08-29T18:48:25.195300Z

IMO GraphQL made the right tradeoff: return a plain JSON tree so that any frontend can construct a query & get the response with plain fetch

phronmophobic 2020-08-29T18:50:39.195500Z

sounds good. my point wasn't that it's the best solution for every problem, it's that we already have a recipe for representing graphs that some users have had success with

1
Aron 2020-08-29T19:06:32.195800Z

What I don't understand is, why would I use graphql when there is datalog?

Aron 2020-08-29T19:08:09.196Z

or falcor. In 2015 I evaluated both and we decided to use graphql, we have gone trough all what you have discussed above and in the end I still had to do lots of pre and postprocessing on the js side to deal with the results I got from our implementation of GraphQL. Not to mention all the non-standard extensions to it.

Aron 2020-08-29T19:08:41.196200Z

Falcor appeared to me back then as something that was started and then abandoned, or at least the public stuff was not very ready for usage at all.

lilactown 2020-08-29T19:18:43.196400Z

the problem with datalog is that it’s not amenable to front end caching

lilactown 2020-08-29T19:19:36.196600Z

graphql (and to a certain extant, eql) can be statically analyzed to determine what entities your query returns, and use that to track whether to use the cache & subscribe to changes to those entities

lilactown 2020-08-29T19:21:58.196900Z

you basically use typename + ID to figure it out, e.g.:

query {
  user(id: 1) {
    ...
  }
}
since GraphQL is typed, you can know that the user query returns a User type, and then any other query that returns information of User type with id equal to 1 will update the same entity in the cache.

lilactown 2020-08-29T19:22:54.197100Z

if you have a front end framework that uses a cache like this, you can skip the request entirely if you already have data

Aron 2020-08-29T19:23:11.197300Z

is helix considered a frontend framework? 🙂

Aron 2020-08-29T19:23:16.197500Z

I know react is not one.

lilactown 2020-08-29T19:23:37.197700Z

helix is just a wrapper for React

lilactown 2020-08-29T19:23:55.197900Z

I’m referencing something like apollo-client or facebook’s relay

lilactown 2020-08-29T19:24:29.198100Z

using a cache like this also allows you to subscribe to queries. so you can use the query above to subscribe to any changes of User w/ id: 1

Aron 2020-08-29T19:25:02.198300Z

Right, so my problem is that all this is conditional that 1. I can implement what is necessary 2. it's easier to implement this than add caching to datalog

Aron 2020-08-29T19:27:02.198500Z

regarding 1. I can, but it was hard last time and no one said anything since (in fact, since then I met ten times as many companies who wanted to ditch GraphQL than those who wanted to adopt it) 2. I don't think it's that hard to add caching to datalog, you just need collaborating environments on both end, with deterministic hashing for values. Then you can hash your queries and responses to the queries and only send the diff.

lilactown 2020-08-29T19:27:18.198700Z

all of this is pretty much impossible to do with general datalog because datalog has certain dynamic properties which prevent you from statically analyzing it. e.g.:

'[:find ?name
  :where
  [?e :config ?name-attr]
  [?e ?name-attr ?name]]

Aron 2020-08-29T19:27:40.199Z

but I do not need to statically analyze a query to cache the results to it

lilactown 2020-08-29T19:28:08.199200Z

you do if you want to relate the query to entities within the cache, to know whether you should fetch or use the cache

Aron 2020-08-29T19:28:25.199400Z

I am not sure what you mean

Aron 2020-08-29T19:28:59.199600Z

I don't 'fetch or use the cache' I always fetch. The cache only sends what's necessary.

lilactown 2020-08-29T19:30:02.199800Z

you can do really simple caching that says “the exact same query will yield the same data”, but you can’t do something like: Query 1: I want the user 1s name, age, and phone number … around the same time, another query … Query 2: I want the user 1s name & phone number w/ GraphQL, you can easily return the cached value for the second. w/ datalog, it can be difficult to know that these two queries are about the same thing

Aron 2020-08-29T19:30:52.200Z

of course you can do that, why wouldn't you be able to do that?

Aron 2020-08-29T19:31:15.200200Z

I assume the queries come from components of the application?

lilactown 2020-08-29T19:31:17.200400Z

because of the reason I said above: datalog is too dynamic to do this for any query

p-himik 2020-08-29T19:31:30.200600Z

With GraphQL and the scenario that you describe, how do you guard against situations where between Query 1 and Query 2 the value of e.g. the user 1's phone number was changed?

Aron 2020-08-29T19:31:59.200800Z

there is probably a timeout or something

lilactown 2020-08-29T19:32:14.201300Z

@p-himik you can control this. if you want to fetch fresh data you can always request it. but if you’d rather use stale data, you can do that too

Aron 2020-08-29T19:32:48.202200Z

I do not do it this way. I do not keep a dedicated cache on the client side separate from client data. The cache is online and sends only updated values. I always send full queries and last timestamps so that the server can know what has changed since.

lilactown 2020-08-29T19:33:05.202700Z

more importantly is that you can know that new data is being loaded. so you can run query 1 first, and then a subscriber to query 1 can get notified when query 2 is fetching fresh data

lilactown 2020-08-29T19:33:30.203700Z

or it can ignore that if it chooses, but it will get updated w/ fresh results once query 2 resolves

Aron 2020-08-29T19:33:35.204Z

This is exactly the kind of programming I want to avoid. Having to think about which query is being loaded when.

lilactown 2020-08-29T19:34:35.204800Z

it’s all about controlling the UX based on what data has already been loaded. e.g. you could show part of the page while some of the other data is loading, if you’ve already populated the cache with some of it

Aron 2020-08-29T19:35:14.205Z

but I do not want to control the ux based on the data, the data already controls the UX, no special wiring, no interfacing, no additional metada over this is required?

lilactown 2020-08-29T19:35:31.205200Z

anyway, my point is that datalog can run e.g. arbitrary functions in the query. it can dynamically choose keys to use based on other parts of the query. it’s too dynamic

Aron 2020-08-29T19:35:59.205400Z

and my point? 🙂

lilactown 2020-08-29T19:36:05.205600Z

so you can’t use as simple of a solution as reading the query and knowing what entities it relates to

Aron 2020-08-29T19:36:13.205800Z

because yours was definitely expressed clearly several times already

lilactown 2020-08-29T19:36:50.206Z

you’re basically saying, “the complexity you’re talking about doesn’t matter because it doesn’t match the way that I want to make my apps”

lilactown 2020-08-29T19:37:17.206200Z

which is fine to say for yourself, but I’m talking about a general application solution a la fulcro, relay, & apollo

lilactown 2020-08-29T19:37:26.206400Z

I have needs for my applications that using datalog does not meet

lilactown 2020-08-29T19:37:40.206600Z

I’m trying to explain the constraints which prevent me from using it

lilactown 2020-08-29T19:37:53.206800Z

and you’re saying they don’t matter. so excuse me for not directly addressing your point

Aron 2020-08-29T19:38:16.207Z

let me give you a tip. every time you consider saying or writing "you are basically saying" to stop and think if the person you are talking to does really say that

Aron 2020-08-29T19:38:50.207200Z

because that's absolutely not what I was saying, not even close

p-himik 2020-08-29T19:42:59.207400Z

What were you saying? Because I've been following the discussion and so far what lilactown has said is completely in line with my own understanding of what's going on.

Aron 2020-08-29T19:44:05.207600Z

What he said is not out of line with my own understanding either.

lilactown 2020-08-29T19:44:46.207900Z

then I don’t understand what you’re saying. > I do not do it this way. > This is exactly the kind of programming I want to avoid. > but I do not want to control the ux based on the data there is either a misunderstanding on my end or yours. I’m trying to explain use cases which I have encountered in my day-to-day, and your rhetorical method has left me with the feeling that you don’t understand or don’t care about them enough to the point where you think that it’s not worth talking about. if datalog matches your use cases well, then you should continue to use it. I will continue to not use it, because it does not match mine.

Aron 2020-08-29T19:45:01.208100Z

you are quoting wrongly

Aron 2020-08-29T19:45:54.208300Z

the last line requires the part where I say 'it already does that', that's why I don't 'want' it, since wanting something that is given already by default is impossible.

Aron 2020-08-29T19:51:28.208500Z

I do not want to cast doubt on your use cases. Nor do I want to change how you work. If anything, my part in this discussion was first to ask why GraphQL fits your needs, to this you said things that I understood implicitly in a way that I questioned them. So it's one thing to state 'my day-to-day' and another to say that 'datalog is too dynamic'. I cannot question your day-to-day, but I can question that it's not possible to write the same thing you do - but differently - with datalog. I understand completely why doing the same thing the same way wouldn't work with datalog, you really overexplained that. Maybe I missed it in the long discussion, but have you touched on why you need to have a separated cache on the client side? Isn't it the case perhaps - and pardon me for being egotistical enough to guess - here: that you have been given the tools first then you had to work out the solution? If you already use all the things required for GraphQL like Relay or apollo (or similar), if you already have a UX toolkit that falsely assumes that everything requires a cache and a fetch and some loader, then all this can just happen without notice.

lilactown 2020-08-29T19:52:40.208700Z

there’s too much nuance to what we’re talking about to boil it down to what you’re saying. different applications (or parts of an application) may need different behavior. e.g. if you click a back button, I may want to show cached data. if you navigate to a page by clicking a link, I may want to show some of the data that’s cached but also fetch new stuff. OR I may want to show a loading spinner until all data has completed. this requires knowledge at the call site of a query of what specific behavior you want and the action that brought it there. it’s complicated. to support all of these different behaviors, I need a way of knowing what entities a query refers to before I fetch the query from the server.

lilactown 2020-08-29T19:56:14.208900Z

yes in order to maintain consistency across the UI, I need a front end cache. even just the use case of: Component A renders & fetches some data Component B renders & fetches the same data Component B re-fetches and renders with new data unless I’m okay with Component A and Component B being out of sync, you need a single place to put data that Component A and B can both refer to

Aron 2020-08-29T19:56:55.209200Z

they all read the same state, they can't be out of phase unless I am doing some react experiments

lilactown 2020-08-29T19:57:30.209400Z

so that state then is your cache. it doesn’t have to be outside of the React tree

Aron 2020-08-29T19:57:39.209600Z

It's probably worth noting that I completely agree that this is nuanced and complicated.

Aron 2020-08-29T19:59:35.209800Z

But my attitude after this realization is to 1. avoid general solutions 2. avoid solutions that require those who work on the visuals and interactions to think about network requests and data fetching

Aron 2020-08-29T20:00:02.210Z

and no, the react state is not the cache, that would be the register

Aron 2020-08-29T20:01:29.210200Z

my cache is the same cache that was always the cache, sometime the website can't handle the requests and stores the calculated responses. this is what we called the cache, not the one from the cpu analogy. This cache is definitely nothing like react state.

p-himik 2020-08-29T20:03:56.210400Z

"Cache" is a concept, not a specific place. Arguing semantics is not very productive.

Aron 2020-08-29T20:04:30.210600Z

anyway, thanks for the answers. I now understand that you have decided first that you need a client side cache and then used a solution for this problem. I have no arguments in this area - obviously.

Aron 2020-08-29T20:05:43.210800Z

Yes, I do realize that arguing semantics is not productive, however we have already been in an unproductive discussion and realizing that we meant slightly different things by the same word, in this case making what we meant by it clear helped to make me understand the other side much better.

👍 1
zackteo 2020-08-29T22:29:30.212100Z

Hey guys! Not sure if this is the right channel for this but wanted to get some thoughts. So I happened to see this Clojure question on Stackexchange via Zulip. And thought hey maybe I could give a shot at answering. Okay, wanted to actually comment at first, given that the qn wasn't too clear ... but couldn't cause I haven't accumulated enough rep >< So granted, I ended up giving a short half answer/half question in my original response which okay fine I got 1 downvote for. But because of the comment not to ask questions, I rephrased my answer and gave quite a bit of due thought to trying to tackle it? But, I ended up finding that 2 people deleted my answer? 😕 Maybe it is because an answer can't be provided before clarification? I don't know? Just me trying to rationalise if I am justified to feel a bit angry or annoyed. Any thoughts? 😮 (Also should I undelete my answer? Also moved this from #clojure ) https://stackoverflow.com/questions/63642728/how-to-convert-from-a-string-into-an-argumentfunction-using-clojure?answertab=active#tab-top

❤️ 1
Jcaw 2020-08-30T13:22:29.217200Z

I bet you they removed clojure because it was consistently topping the "most lucrative languages" measure

😁 1