Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default clauses for datalog #37

Open
li1 opened this issue Feb 4, 2019 · 5 comments
Open

default clauses for datalog #37

li1 opened this issue Feb 4, 2019 · 5 comments
Assignees
Labels
enhancement New feature or request

Comments

@li1
Copy link
Member

li1 commented Feb 4, 2019

To prevent a situation where queries fail simply due to missing data, it might make sense to introduce default values (similar to get-else in datomic).

@li1 li1 added the enhancement New feature or request label Feb 4, 2019
@comnik
Copy link
Member

comnik commented Feb 4, 2019

Agreed. We won't be able to implement get-else as a transform. We could either interpret the whole of [(get-else $ e :a v v-else) ?v] as a special pattern or deviate from the Datomic API and offer something like [?e :a (or ?v false)]. With the new binding system, we might even be able to do something like [?e :a ?v] (default ?v false), where the default binding would work like a constant binding without any validation.

@li1
Copy link
Member Author

li1 commented Feb 4, 2019

I like the latter two, with the second one perhaps giving us the most flexibility.

E.g., if we want to calculate a balance, but some users haven't paid / received anything yet so their respective queries currently don't evaluate:

(def balance
  '[:find ?person ?balance
    :where
    (conj/paid ?person ?paid)
    (conj/recv ?person ?recv)
    [(subtract ?recv ?paid) ?balance]])

With third suggestion:

(def balance
  '[:find ?person ?balance
    :where
    (conj/paid ?person ?paid)
    (default ?paid 0)
    (conj/recv ?person ?recv)
    (default ?recv 0)
    [(subtract ?recv ?paid) ?balance]])

Second suggestion looks weird if used on the rules / subqueries (it certainly doesn't look like we're binding 0 to ?paid):

(def balance
  '[:find ?person ?balance
    :where
    (conj/paid ?person (or ?paid 0))
    (conj/recv ?person (or ?recv 0))
    [(subtract ?recv ?paid) ?balance]])

...but using it on the subtract clause makes sense:

(def balance
  '[:find ?person ?balance
    :where
    (conj/paid ?person ?paid)
    (conj/recv ?person ?recv)
    [(subtract (or ?recv 0) (or ?paid 0)) ?balance]])

Also, we could perhaps even expand the (or ...) syntax to binding attributes, like this:

(def balance
  '[:find ?person ?balance
    :where
    (conj/paid (or ?person "alfredo") ?paid)
    (conj/recv ?person ?recv)
    [(subtract (or ?recv 0) (or ?paid 0)) ?balance]])

This would still leave the binding of ?person for ?recv, so it means something like "if ?person hasn't paid anything, use ?paid from Alfredo, but keep ?person's receivables."

With suggestion 3 we would sacrifice that granularity and could only speak about ?person in more general terms (at least without building a more complicated query):

(def balance
  '[:find ?person ?balance
    :where
    (conj/paid ?person ?paid)
    (default ?paid 0)
    (conj/recv ?person ?recv)
    (default ?recv 0)
    (default ?person "alfredo")
    [(subtract ?recv ?paid) ?balance]])

@comnik
Copy link
Member

comnik commented Feb 4, 2019

I prefer suggestion 3, because it is no new syntax, just a new type of binding. The "alfredo" use case seems highly confusing, because it breaks the unification intuition (?person suddenly doesn't refer to the same entity in all cases).

And another (approximate?) way of expressing that could be:

(conj/paid ?person ?paid)
(conj/recv ?person ?recv)
(conj/paid "alfredo" ?paid-alfredo)
(default ?paid ?paid-alfredo)

Which on the front-end seems like an elegant solution ((default ?paid <constant>) would simply de-sugar to (default ?paid ?gensym) (constant ?gensym <constant>)). Not quite sure how the implementation as a PrefixExtender would look like in that case...

@comnik
Copy link
Member

comnik commented Feb 17, 2019

Some more findings, mostly intended as memo.

Default can't be something associated with just a symbol. E.g. (default ?v fallback) doesn't make sense, because as with negation, defaults need to be w.r.t to a set of entity ids.

I'll try and see how a (default ?e ?v fallback) feels.

@comnik
Copy link
Member

comnik commented Feb 17, 2019

So comnik/declarative-dataflow@1a65b0f adds experimental support for something like this.

Indeed it is both simpler and more complex than expected.

More complex, because (for new-query-engine reasons) default bindings are not just dependent on entity ids for context, but also on the attribute that they are acting on.

Simpler, because now [?e :a ?v :else default] actually becomes a pretty good expression of just that and the implementation fits in nicely with the new query engine (as far as I can tell).

My initial worries were unfounded, because when validating other bindings, this binding will already have a set of entity ids for context.

(Of course this means that we'll have to wait with frontend support, until the new query engine is the default.)

@comnik comnik self-assigned this Feb 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants