Revocation in Racklog

It's been suggested I look into something like Racklog
to store the state data base of a text adventure game.

But I see a problem. Actions in the game can change the state of the world.

Racklog has operations like

(define %knows
  (%rel ()
    [('Odysseus 'Tex)]
    [('Penelope 'Tex)]

But what if you decide that Penelope no longer %knows Tex?

Is there a way to remove one entry from a relation?

I'm not looking for something that is done during a deduction.
I'm looking for a way of changing the world between deductions.

Or in data base terms -- I don't need to change a data base during a query.
But I'd like to be able to change it after one query and before another
without having to recreate the whole thing from scratch.

-- hendrik

1 Like

The Racklog documentatino explicitly says there's no mechanism for taking an entry from a relation because it's easy to save previous relations as ordinary values and bring them back later.
But this assumes I want to use the entries in a stack-like fashion.
I don't. I want to be able to remove any arbitrary entries in a relation, and to add others to replace them.
I have no need ever to go back to previous versions.

It looks as if I'm going to have to take Racklog apart and put it together differenty if I'm going to accomplish this.

Does anyone have alternative logic engines for me to look at?

-- hendrik

how about Parenlog ?

This package contains an implementation of a language very similar to pure Prolog, except with parenthetical notation.

And there is MiniKanren: logic programming in Scheme (but it is in Racket)


Thanks for popinting those out.
I've now looked at those. At least, I've read the documentation.
All of them appear to have the same flaw as Racklog.
We have axioms and queries.
They use logical deduction from axioms to answer queries.
So far, OK.
But the mechanisms that define the set of axioms are all macros and so run at so-called compile-time..
There seems to be no mechanism to construct an axiom or a set of axioms at run-time and feed it into the logic engine.
I will need to create, add, remove, and change axioms at run-time. (But I don't need to do this while deductions are actually taking place.)
So to use any of these packages I either have to

  • take them apart and build run-time equivalents that correspond to what they do at macro-time, or
  • find some hitherto unknown (to me) mechanisms within Racket that can finesse this to perform macro expansion on expressions calculated at run-time and then run the expanded code at run-time.

-- hendrik

At least in miniKanren, you can mix Racket code with miniKanren goals (relations):

#lang racket
(require "mk.rkt")

(define knowledge-db
  '((Odysseus Tex)
    (Penelope Tex)))

(defrel (knows a b)
  (member/o `(,a ,b) knowledge-db))

;; Who does Penelope know? => '(Tex)
(run* q (knows 'Penelope q))

;; Who knows Tex? =>  '(Odysseus Penelope)
(run* q (knows q 'Tex))

;; Remove Penelope from the knowledge database
(set! knowledge-db
      (filter (lambda (k) (not (eq? (car k) 'Penelope))) knowledge-db))

;; Who does Penelope know? => '(), empty list, that is "no one"
(run* q (knows 'Penelope q))

;; Who knows Tex? =>  '(Odysseus)
(run* q (knows q 'Tex))

Note that the code above uses defrel from "The Reasoned Schemer" 2nd edition, as well as the member/o relation defined in in the same book. If you want a MK implementation in which the above code will run directly, you can find one here, otherwise, the code will need small tweaks to run with other miniKanren implementations.

miniKanren also allows defining goals (relations) as normal Racket programs, so you could use anything as your database (including a real SQL database), however, that is more complex to do and requires an understanding of how MK works internally.


Thank you. This is an elegant piece of code that looks as if it will do what I want.
O course I won't know for sure until I actually start using it and see what happens.
One thing puzzles me -- the
on line 216. Really? A comma after the y?

-- hendrik

I think Racket's implementation of Datalog can also do what you want. Here's a version of Alex's example using the embedding into Racket:

#lang racket

(require datalog)

(define db

(datalog db
         ;; Assert facts
         (! (knows odysseus tex))
         (! (knows penelope tex))
         ;; Who does penelope know?
         (? (knows penelope Q)))
;; => '(#hasheq([Q . tex)])
(datalog db
         ;; Who knows tex?
         (? (knows Q tex)))
;; => '(#hasheq([Q . odysseus]) #hasheq([Q . penelope]))
(datalog db
         ;; Retract the assertion that penelope knows tex
         (~ (knows penelope tex))
         ;; Who does penelope know?
         (? (knows penelope Q)))
;; => '()
(datalog db
         ;; Who knows tex?
         (? (knows Q tex)))
;; => '(#hasheq([Q . odysseus]))

(You can also write stand-alone Datalog programs either with traditional syntax in #lang datalog or with parenthetical syntax with #lang datalog/sexp.)

See especially the very end of the 4 Racket Interoperability page for details on the automatic quoting and how to escape from Datalog terms to normal Racket expressions.

Another alternative for your original question might be Jay's Functional Relational Algebra package, if you prefer functional update (including deletion) instead of a stateful "database". That could be useful for implementing features like "undo" in a game. But you could put together similar functionality using datalog's read-theory and write-theory, I think, or the package could add something like copy-theory and a non-destructive version of the datalog macro.

About datalog:

#, looks useful, but let me quote from 4 Racket Interoperabiity in the datalog manual

External queries are represented as (ext-table term ... :- term ...), where ext-table is an identifier bound to a procedure or #,expr where expr evaluates to a procedure that when given the first set of terms as arguments returns the second set of terms as values.

This requires that the external procedure produces one and only one set of values for the second set of terms. What if more than one set of values is appropriate, so that each can be tested in turn in later datalog calculations? Is there any mechanism for that?

-- hendrik