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/
emccue 2021-03-21T01:49:09.089300Z

has anyone tried out the new foreign linker api?

emccue 2021-03-21T01:49:20.089700Z

I'm having difficulties setting a callback function in a struct

emccue 2021-03-21T01:50:05.090800Z

and i'm unsure if its because I am getting the address of the callback function wrong (it should be a symbol in the library) or something I am doing wrong with struct padding

phronmophobic 2021-03-21T20:10:24.093800Z

There are several query DSLs (GraphQL, EQL, pull syntax, Datalog, meander, SQL, specter, etc) that are fairly generic, flexible, expressive, and independent of datastore (ie. crux, datascript, datomic, in memory). It seems like mutations/updates/transactions should be equally flexible and generic, but I'm having trouble finding many good references. The closest thing I could find is https://github.com/redplanetlabs/specter navigators. Generally, most options for updating or inserting new facts seem less expressive, especially when updating info that refers to data that already exists in the datastore. I've looked at several different options, but compared to guides covering querying, the sections covering mutations/updates/transactions seem like the "then a miracle occurs" part of the documentation: https://docs.datomic.com/cloud/transactions/transaction-processing.html https://netflix.github.io/falcor/doc/DataSource.html#set https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/ https://pathom3.wsscode.com/docs/mutations https://day8.github.io/re-frame/api-builtin-effects/#db https://github.com/mpdairy/posh#transact https://opencrux.com/reference/21.02-1.15.0/transactions.html https://github.com/cljfx/cljfx#event-handling-on-steroids https://book.fulcrologic.com/#Mutations https://edn-query-language.org/eql/1.0.0/specification.html#_mutations Are there any good resources or ideas I'm missing?

2021-03-21T20:14:28.093900Z

Asami seems to have a slightly different take on transactions than some of its datalog peers: https://github.com/threatgrid/asami/wiki/Transactions I'm not familiar with it enough to comment on how it compares in terms of expressivity though

1👍1👀
lilactown 2021-03-21T21:38:05.094400Z

I think that graphql and pathom mutations are more general than updating a datastore, although they are often used for this. they can be used for any kind of side-effecting procedure.

phronmophobic 2021-03-21T21:51:25.094800Z

That's true. I most interested in just the datastore update part, but having a story around combining side effects with updates (eg. the coordination between Agent's send and STM transactions) would be neat too. Other than specter, I can't find many resources that have much documentation/examples on how to set, update, and compose changes.

2021-03-21T21:51:43.095Z

The tricky thing is most of those query dsls are based on the semantics of triple stores, and a triple store is a kind of universal encoding

2021-03-21T21:52:19.095200Z

So all sql tables can be represented sets of triples

phronmophobic 2021-03-21T21:52:34.095400Z

Even if the references only pertained to triple stores, that would be helpful

2021-03-21T21:53:39.095600Z

And in that model a transaction would be a set of assertions and retractions of triples

2021-03-21T21:54:33.095800Z

But not all sets like that can be mapped to a sql table (for example)

2021-03-21T21:55:00.096Z

You can't just retract a single attribute, you have to retract the whole row or nothing

phronmophobic 2021-03-21T21:56:21.096200Z

Right. Assertions and retractions seems kind of low level (especially compared to the query side). In theory, it seems like it should be possible to • given a specter path, a query, and a schema • automatically translate that into a transaction that targets any of the available datastores

phronmophobic 2021-03-21T21:57:13.096400Z

like you can run datalog against any datastore, or just an in memory datastructure

phronmophobic 2021-03-21T21:57:53.096600Z

it should be possible to write an update description that is as expressive as specter's navigators that would update any datastore (it already works on in memory data structures)

2021-03-21T22:06:45.097100Z

You will need to make lots of decisions on things like the above mentioned "what if I retract a triple on a sql store?" Is it an error if you don't retract everything else in the same row too? Or does it automatically imply they all get dropped? Or is a nop?

phronmophobic 2021-03-21T22:08:06.097300Z

Are there any options that work just on triple stores?

2021-03-21T22:08:08.097500Z

And as you make decisions about that it makes your approach less universal

2021-03-21T22:09:17.097700Z

Datomic is a triple store

phronmophobic 2021-03-21T22:09:56.097900Z

Right, but afaict, the transaction interface is much less expressive than the query interface

2021-03-21T22:11:06.098400Z

Whoops

2021-03-21T22:11:29.098600Z

They just have to bottom out/expand into a set of assertions and retractions

phronmophobic 2021-03-21T22:12:01.098800Z

right. Are there any tools/libraries that help with that?

2021-03-21T22:12:56.099Z

Dunno

2021-03-21T22:13:50.099200Z

I would really not go with spectres navigators as an example

2021-03-21T22:14:51.099400Z

Like, basically a navigator is just a database query

phronmophobic 2021-03-21T22:14:55.099600Z

To summarize, query feels very well documented (guides, APIs, examples) and expressive. The options for Updates/transactions seem less well documented and expressive.

lilactown 2021-03-21T22:15:42.099900Z

what else would you like to express?

2021-03-21T22:16:03.100100Z

But tightly coupled to a concrete data structure, where a relational/tuple store query language is not

phronmophobic 2021-03-21T22:21:02.100500Z

This is just off the top of my head, so it's not a great example: Given several todo lists (work, home, hobby), toggle all work todo list items created more than a year ago. (Toggle would be logical negation, even though that use case doesn't make sense. I just wanted an example that updated an attribute using a function rather than setting the value) This is just an example, but I'm looking for something that is composable similar to the way queries are composable.

2021-03-21T22:25:19.101Z

Data has logical relationships with other data

2021-03-21T22:26:25.101200Z

But when we use data in programs it has physical relationships (as things are connected in memory etc) as well

2021-03-21T22:26:44.101400Z

And spectre as a tool is way to into physical relationships

2021-03-21T22:28:49.101600Z

the logical basis for query languages is first order logic, the academic treatments of datalog use first order logic directly, and sql does its best

2021-03-21T22:29:47.101800Z

the problem is logic is atemporal while update/assignment/etc requires time

2021-03-21T22:30:52.102Z

so you might look at something like dedalus https://www2.eecs.berkeley.edu/Pubs/TechRpts/2009/EECS-2009-173.html which explicitly models time

phronmophobic 2021-03-21T22:32:36.102200Z

I'm not saying specter is the right answer, but what I like about it is that 1. paths can be reused for either query or transformation 2. complex paths can be composed of simpler paths. Nothing about specter is tied to concrete data structures (it could work with dbs as well). It's more similar to lens in that you just need to implement select and transform. It seems like you should be able to reuse datalog, EQL, etc. for transformation and I was hoping to find an example.

2021-03-21T22:32:57.102400Z

but that is already the case

phronmophobic 2021-03-21T22:33:11.102600Z

can you provide an example?

2021-03-21T22:33:18.102800Z

a datomic query is the "path"

2021-03-21T22:34:04.103Z

so you have a transaction function that runs a query for the todo items, then asserts and retracts them

2021-03-21T22:35:28.103200Z

for a lot of the abstractions you listed, they really don't have transactions, which is why their updates suck, you can't do a read and expect things to be consistent with that read when you write

phronmophobic 2021-03-21T22:37:51.103400Z

maybe I'm just missing something, but I can't find any examples that reify the transaction description. I found this todomvc (which is kind of old), https://github.com/madvas/todomvc-omnext-datomic-datascript/blob/master/src/cljc/todomvc/queries.cljc#L48

phronmophobic 2021-03-21T22:38:52.103700Z

1. It's doesn't even do toggle, it reads and then writes in two separate transactions. 2. It's not composed of simpler pieces

lilactown 2021-03-21T22:39:01.103900Z

one of the things that makes query languages like datalog, EQL, SQL etc. "worth it" are the fact that you can give them to another process or machine to run. you're essentially moving the compute of the result to where the data lives. datomic and datascript IME run under an assumption that whatever you're modifying can fit in memory, and to just tell them the facts that have changed - and then transaction functions fill in the gap when that's not the case.

2021-03-21T22:39:39.104100Z

yeah, I mean, random code on github is terrible 🙂

lilactown 2021-03-21T22:40:16.104300Z

maybe some other corners of tech that deal with writing/updating large (i.e. won't fit in memory) data would yield some more ideas?

phronmophobic 2021-03-21T22:40:29.104500Z

Do you have an example that you would recommend?

2021-03-21T22:40:41.105Z

grrr

2021-03-21T22:40:46.105200Z

how do I keep hitting that checkbox

1
lilactown 2021-03-21T22:41:20.105500Z

I mean, first thing that comes to mind is SQL 😛

2021-03-21T22:41:33.105700Z

it shows the transaction function takes a db as an arg, and that the result of the function is a list of assertions and retractions

phronmophobic 2021-03-21T22:42:06.105900Z

yea, SQL actually does have a way to combine the query with an update. I was assuming EQL, datalog, etc would also

2021-03-21T22:42:12.106100Z

the db that the tx function takes can be queried etc

phronmophobic 2021-03-21T22:43:38.106300Z

I know that it's theoretically possible. I was hoping to find some references/guides/anything where someone is already doing that

2021-03-21T22:44:37.106700Z

Dunno, I just know that exists from watching datomic talks, I've never used datomic

2021-03-21T22:47:11.106900Z

We do have some code at work that treats a database row as an atom, it implements IAtom and does a compare and set in the database of the row contents

1👀
2021-03-21T22:47:30.107100Z

Which is a similar kind of thing in the small

2021-03-21T22:49:33.107300Z

You want to express something like given some formula that is true for this version of the database this other formula must true in the next version

2021-03-21T22:51:21.107500Z

The first formula is used to query, and find all the triples that make it true, the second formula needs to generate a set of assertions and retractions that would make it true

2021-03-21T22:51:52.107700Z

And then you actually need some way to talk about different versions of a database (transactions)

phronmophobic 2021-03-21T22:52:26.107900Z

There's a few different types of updates with increasing complexity 1. updating a specific attribute 2. updating multiple attributes on a single entity 3. updating an entity's attribute and the attribute of another entity related through a foreign key attribute 4. Updating several entities related that may be linked through attributes. The first 2 are fairly straightforward. Starting with 3), it gets more interesting

phronmophobic 2021-03-21T22:52:54.108100Z

> And then you actually need some way to talk about different versions of a database (transactions) Most databases let you just put everything in a single transaction, right?

lilactown 2021-03-21T22:55:12.108300Z

I think the insight is that the language needed to specify the kinds of updates you'd want to do is close to the level of clojure.core

2021-03-21T22:55:40.108500Z

All of those updates boil down to the same thing

lilactown 2021-03-21T22:55:55.108700Z

and at that point, why not ship your database with clojure.core? hence, transaction functions

2021-03-21T22:56:07.108900Z

Assertions and retractions of triples

2021-03-21T22:57:49.109100Z

It depends, but I believe most of those query dsls things can run against anything

phronmophobic 2021-03-21T22:57:50.109300Z

I agree. Are there examples of using EQL, GraphQL, datomic, datalog where the transactions updates are constructed of simpler pieces?

lilactown 2021-03-21T22:58:33.109500Z

EQL and graphQL are not concerned with updating a store

lilactown 2021-03-21T22:58:49.109700Z

their "mutations" are essentially function calls

phronmophobic 2021-03-21T22:59:29.109900Z

ok, for applications that use those query languages, do they compose their transactions of simple pieces?

lilactown 2021-03-21T22:59:36.110100Z

no

lilactown 2021-03-21T22:59:49.110300Z

not from the client perspective, anyway

2021-03-21T22:59:59.110500Z

I dunno, maybe ask in #datomic

phronmophobic 2021-03-21T23:00:12.110700Z

will do

lilactown 2021-03-21T23:00:39.111Z

IME most graphQL and EQL services run some procedure on the backend that executes some SQL, or calls another service that updates some store

2021-03-21T23:01:17.111200Z

Yeah graphql very much does not have transactions

2021-03-21T23:01:29.111400Z

Mutations are the wild west

lilactown 2021-03-21T23:02:38.111600Z

which makes sense on the whole. a lot of systems are built in a CQRS style and so updating a store might look nothing like querying it beyond the facade of a service call

phronmophobic 2021-03-21T23:02:41.111800Z

If queries can be generic, flexible, and composable, is it the wrong question to ask if updates/requests/mutations can be structured to be generic, flexible, and composable?

lilactown 2021-03-21T23:03:59.112Z

I think that we generally accept more restrictions on how we express our reads than how we want to update something

2021-03-21T23:04:23.112200Z

I dunno, the whole thing is fraught because many of those tools are not just pass throughs, they can transform and merge data from multiple sources

2021-03-21T23:04:46.112400Z

It is hard enough doing transactional updates against a single store

phronmophobic 2021-03-21T23:05:55.112600Z

We used to create REST apis for querying that were bespoke with the same justification. It seems at least plausible that specifying updates could have more structure.

lilactown 2021-03-21T23:06:25.112800Z

like, EQL is strictly less expressive or powerful than datalog. but it meets a lot of use cases, and that lack of power is useful in many cases

lilactown 2021-03-21T23:07:39.113Z

if I were using a language that allowed me to declaratively express updates, does the costs in power outweigh the benefits?

2021-03-21T23:07:51.113200Z

I think it could be possible with a good transactional relational store

2021-03-21T23:08:12.113400Z

But almost all of your links are not one of those

2021-03-21T23:08:43.113600Z

They are middleware for presenting a particular style of query api

phronmophobic 2021-03-21T23:09:41.113800Z

I'm sure there's a better approach than starting from scratch and building a custom solution every time. Are there any good examples for handling the update side that let you start with more than low level mutations?

2021-03-21T23:09:50.114Z

Graphql isn't even relational, and has some many odd bits, I am not even sure how you could infer how to call mutations to update data returned by a query

phronmophobic 2021-03-21T23:11:25.114200Z

GraphQL gives you a result. Assuming you have a schema, the original query, and can state what changes you would like, it should be possible to generate a transaction

2021-03-21T23:12:07.114400Z

With something like datalog, a query (a formula) can be used both to search a database for a set of facts that make it true, or (this you see mostly in prolog or core.logic) it can be used to generate the set of facts that make it true

2021-03-21T23:12:24.114600Z

Nah, graphql doesn't work like that

2021-03-21T23:12:45.114800Z

A mutation is basically an arbitrarily named rpc

phronmophobic 2021-03-21T23:13:20.115Z

Right. I'm not saying it helps you do that. I'm saying it would be possible to write a library that allows that

2021-03-21T23:13:43.115200Z

So an update can in theory be a pair of formula (queries),

2021-03-21T23:14:20.115400Z

One query to find facts that match another used to generate changes to be asserted

phronmophobic 2021-03-21T23:15:00.115600Z

I think so, you would also need either an update function or a new value. I think you might also need a schema to make sure you include unique ids.

lilactown 2021-03-21T23:15:28.115800Z

@smith.adriane how would you specify an update like, "For each user, capitalize the first letter of every part of a name except <blacklist words>"

2021-03-21T23:16:08.116Z

In theory no, because the bindings from first query should fill in any unknowns in the second

phronmophobic 2021-03-21T23:17:41.116700Z

I'm imagining some syntax like: (update user-name-query capitalize-parts)

lilactown 2021-03-21T23:18:11.116900Z

how would the remote server know what capitalize-parts is?

phronmophobic 2021-03-21T23:18:34.117100Z

what remote server?

lilactown 2021-03-21T23:20:07.117300Z

I guess that's not a req. then I'll ask slightly differently, how is capitalize-parts implemented?

phronmophobic 2021-03-21T23:20:25.117500Z

regular clojure code

phronmophobic 2021-03-21T23:21:00.117700Z

#(-&gt;&gt; % split (map capitalize-if-not-blacklisted) join)

lilactown 2021-03-21T23:21:30.118100Z

ah. i thought you were after something more declarative / composable

phronmophobic 2021-03-21T23:22:19.118300Z

it's really being able to specify which entities/attributes that I was looking for composability

2021-03-21T23:22:28.118500Z

it wold be something like (update &lt;query&gt; (fn [&lt;bindings from query that includes names&gt;] &lt;some-new-query-that includes the names as capitalize&gt;))

phronmophobic 2021-03-21T23:23:18.118700Z

I don't understand the new-query part. How does that work?

2021-03-21T23:23:39.118900Z

a query in datalog is a statement is first order logic

phronmophobic 2021-03-21T23:23:43.119100Z

wouldn't the function just return the names capitalized

2021-03-21T23:23:56.119300Z

the result of the query is the set of data that makes that formula true

2021-03-21T23:24:34.119600Z

but you can reverse that and say, from this statement in first order logic, generate the data that makes it true

2021-03-21T23:25:21.119800Z

so the first query is run "forwards" as a query to find the data you want to make changes to, the second query is run "backwards" to generate the changes you need to make

phronmophobic 2021-03-21T23:25:31.120Z

I guess I was thinking more like:

(update user-name-query (map capitalize-parts))

2021-03-21T23:26:02.120200Z

maybe, it depends a lot on the structure of your results

lilactown 2021-03-21T23:26:09.120400Z

I think you also need some way of validating that the result of the query matches whatever the update fn expects

2021-03-21T23:26:25.120600Z

a query is self validating

lilactown 2021-03-21T23:26:42.120800Z

an update is destructive. invalid updates could trash your data

2021-03-21T23:26:42.121Z

e.g. if the result doesn't match it is a bug in your query, so fix it

2021-03-21T23:27:00.121300Z

same with update-in

lilactown 2021-03-21T23:27:08.121500Z

sure, I'm talking more operationally

lilactown 2021-03-21T23:28:15.121700Z

I guess I'm stuck in this thinking that, most of these query langs (EQL, GraphQL, datomic datalog) exist in systems that have a server-client relationship. often, GraphQL and EQL are exposed to the internet. so there's some adversarial thinking that goes into the design as well.

lilactown 2021-03-21T23:28:45.122Z

maybe phronmophobic doesn't need that in his design

2021-03-21T23:28:53.122200Z

almost none of these things is strictly a first order logic langauge, so you may only be able to support subsets of them

phronmophobic 2021-03-21T23:28:54.122400Z

I don't need that in my design

2021-03-21T23:29:40.122600Z

yeah, I think the question is flawed, in the way it conflates things that are not at all alike (databases and api middleware layers)

2021-03-21T23:30:06.122800Z

for api middleware layers, the fact that mutations are only exposed in an adhoc way is almost a feature

1💯
lilactown 2021-03-21T23:30:22.123Z

the other consideration in EQL and GraphQL are that often they are often layers in front of many different stores, and it takes special care to ensure the relative correctness of what is basically a distributed transaction 😅 (which is basically what hiredman is saying)

phronmophobic 2021-03-21T23:30:54.123400Z

I agree that receiving updates from an untrusted client requires more adversarial thinking. Building mutations from scratch doesn't seem like a bonus

phronmophobic 2021-03-21T23:32:28.123600Z

It would be great to have a solution that is as flexible and generic as EQL, datalog, etc. I would settle for a solution that works locally with a trusted client with datascript

lilactown 2021-03-21T23:32:38.123800Z

like what if the way that your system reads data is from a SQL store, but the way you write is by putting a bunch of messages in kafka?

lilactown 2021-03-21T23:35:00.124100Z

then I'm in agreement that the documentation for datascript is woefully lacking 😛

phronmophobic 2021-03-21T23:36:08.124300Z

If there's an example for any of crux, autonormal, datomic,etc, that would be a good start as well.

lilactown 2021-03-21T23:40:35.124500Z

you could probably do something like this in datascript:

(defn transact-q! [conn q f]
  (swap! conn (fn [db] (ds/db-with db (f (ds/q q db)))))

phronmophobic 2021-03-21T23:41:13.124700Z

datascript also has transaction functions

lilactown 2021-03-21T23:41:23.124900Z

where q is some datalog and f is some function that takes the result of the query and returns tx-data

lilactown 2021-03-21T23:41:47.125100Z

ah I tried googling if datascript had arbitrary tx fns and couldn't find it

phronmophobic 2021-03-21T23:41:54.125300Z

yea, that's more or less the plan. I was hoping there would be some prior art that I could cheat off of

lilactown 2021-03-21T23:42:32.125700Z

autonormal you could do basically the same. autonormal doesn't have the notion of a conn, so you can handle the transactional guarantees however you like

lilactown 2021-03-21T23:43:48.125900Z

but yeah, pathom and GraphQL aren't really meant to be fronts to a single store. autonormal is more of a utility library for reading, it doesn't deal with the whole time thing that hiredman was talking about

phronmophobic 2021-03-21T23:46:41.126200Z

what I like about pathom is that it focuses on just specifying what data you need rather than how to get it. I still think it's possible to use something that's pathom like to say what changes you want without specifying how to run them.

phronmophobic 2021-03-21T23:47:08.126400Z

in addition to queries being generic and composable

phronmophobic 2021-03-21T23:49:22.126700Z

I don't need a generic way to handle multiple data stores, but I do plan on implementing a way to have some state in memory with an option to have some state stored more durably.

2021-03-21T23:49:44.126900Z

Interesting convo. I don't have much to add but I did think of another example of a query/transform library that might be useful as another point of reference: Odin https://github.com/halgari/odin#transforming-data

1🙌