Share your thoughts in the 2024 State of Clojure Survey!

Welcome! Please see the About page for a little more info on how this works.

0 votes
in core.logic by

So, tagged data structures are probably interesting in a relational context. Say you have a relation with some default logic about dogs:

`
(defna friendlyo [Dog-Or-Breed]

([:Spot] succeed)
([:Spike] fail)
([Other-Dog] (fresh [Breed] (dog-breed Other-Dog Breed) (friendlyo Breed)))
([(breed :miniature-dachshund)] fail)
([(breed :golden-retriever)] succeed)
;. . .)

`

Assume there's a (defmacro breed (link: t) `(link: :breed ~t)).

That's nicer than having to drop (link: :breed :golden-retriever) in there or whatever, since it's compile-time-checkable, less error-prone, reduces duplication, etc.

This little patch makes ex* expand macros in patterns so it doesn't treat e.g. (breed :golden-retriever) as introducing a new LVar called "breed". Test also provided.

10 Answers

0 votes
by

Comment made by: dnolen

I'm surprised that this doesn't already work. We have support for unifying expressions in the pattern already. Look at line 1230 in tests.clj in the master branch.

So this should just work, no need to explicitly support macros as far as I can tell. If it's not working, then there's a bug.

0 votes
by
_Comment made by: joeosborn_

At least on 0.7.5, matching against a macro gives a runtime error:


Exception in thread "main" java.lang.ClassCastException: clojure.core.logic.LVar cannot be cast to clojure.lang.IFn
    at rl.core$glyph_$fn__123$fn__144$fn__165$fn__166$_inc__167$fn__168.invoke(core.clj:61)
    at clojure.core.logic.Substitutions.bind(logic.clj:211)
    at rl.core$glyph_$fn__123$fn__144$fn__165$fn__166$_inc__167.invoke(core.clj:58)
    at clojure.core.logic$fn__1056$_inc__1057.invoke(logic.clj:1160)
    at clojure.core.logic$fn__1056$_inc__1057.invoke(logic.clj:1160)
    at clojure.core.logic$fn__898$_inc__899.invoke(logic.clj:823)
    at clojure.core.logic$fn__890$fn__891.invoke(logic.clj:828)


Using a fn instead of a macro gives the same:


Exception in thread "main" java.lang.ClassCastException: clojure.core.logic.LVar cannot be cast to clojure.lang.IFn
    at rl.core$drawable_$fn__235$fn__248$fn__249$_inc__250$fn__251.invoke(core.clj:67)
    at clojure.core.logic.Substitutions.bind(logic.clj:211)
    at rl.core$drawable_$fn__235$fn__248$fn__249$_inc__250.invoke(core.clj:65)
    at clojure.core.logic$fn__1056$_inc__1057.invoke(logic.clj:1160)
    at clojure.core.logic$fn__894$_inc__895.invoke(logic.clj:826)
    at clojure.core.logic$fn__1056$_inc__1057.invoke(logic.clj:1160)
    at clojure.core.logic$fn__898$_inc__899.invoke(logic.clj:823)
    at clojure.core.logic$fn__898$_inc__899.invoke(logic.clj:823)
    at clojure.core.logic$fn__898$_inc__899.invoke(logic.clj:823)
    at clojure.core.logic$fn__890$fn__891.invoke(logic.clj:828)


Here's (glyph-) for reference (don't mind all the extra [], I have a weird key/value thing because of some conveniences for maintaining fact identity in a temporal database):

(defna glyph- [Key Val]
    ([[Thing] [Glyph]] (thing- [Thing]) (on-fire_ *turn* [Thing]) (== Glyph \δ))
    ([[Thing] [Glyph]] (thing- [Thing]) (fresh [Type] (type- [Thing] [Type]) (glyph- [Type] [Glyph])))
    ([[(type-enum :player)] [Glyph]] (== Glyph \@))
    ([[(type-enum :dragon)] [Glyph]] (== Glyph \D))
    ([[Type] [Glyph]] (== Glyph \?)))


and type-enum as a macro:

(defmacro type-enum [v] `[:enum :type ~v])


and as a fn:

(defn type-enum [v] [:enum :type ~v])


I'll mess around and see if my example works in HEAD.
0 votes
by

Comment made by: joeosborn

Same exception with this test case in HEAD (sorry for all the facts):

`
(defrel thing- [Thing])
(defrel type- [Thing] [Type])
(fact thing- [0])
(fact thing- [1])
(fact thing- [2])
(fact type- [0] [:player])
(fact type- [1] [:dragon])
(fact type- [2] [:pig])
(defn type-enum [t] [:type t])
(defna drawable- [Key]
([[Thing]] (thing- [Thing]) (fresh [Type] (type- [Thing] [Type]) (drawable- [Type])))
([[(type-enum :player)]] succeed)
([[(type-enum :dragon)]] succeed))

(deftest do-fns-work

(is (= (run* [q] (drawable- [q])) '(0 1))))

`

Now that I look at it, I may be expecting a wrong-format return value, but the point is that I don't even get that far.

Using the REPL, I checked out how (defna drawable- . . .) expands (tidied up slightly):

`
(def drawable- (clojure.core/fn ([Key]
(clojure.core.logic/conda

  ((clojure.core.logic/fresh [Thing] (clojure.core.logic/== [Thing] Key) (thing- [Thing]) (fresh [Type] (type- [Thing] [Type]) (drawable- [Type])))) 
	((clojure.core.logic/fresh [type-enum] 
	  (clojure.core.logic/== [(type-enum :player)] Key) succeed))
	((clojure.core.logic/fresh [type-enum] 
	  (clojure.core.logic/== [(type-enum :dragon)] Key) succeed))))))

`

Note the (clojure.core.logic/fresh (link: type-enum) . . .) forms, which are exactly what I would not want to see in this case.

I'm not really sure why this doesn't work here yet works for the matche test case.

0 votes
by

Comment made by: dnolen

[(type-enum :dragon)]

This pattern make it seem like you want to match:

[[:type :dragon]]

Note extra level of brackets here. Is this the case?

Even so I agree that the expansion doesn't look quite right. We should never descend into a seq form like that.

0 votes
by

Comment made by: joeosborn

Yes, that's exactly the desired outcome in this case--a tagged value in my naive interpretation. Is the reason it fails whereas the test on :1230 doesn't the fact that it's producing a vector and not a list? Changing the fn to return a list instead of a vector didn't seem to help.

My patch, fwiw, doesn't exhibit that behavior (at least for macros, haven't tested it with fns).

0 votes
by

Comment made by: dnolen

What I mean is don't you want the following instead?

(defna drawable- [Key] ([[Thing]] (thing- [Thing]) (fresh [Type] (type- [Thing] [Type]) (drawable- [Type]))) ([(type-enum :player)] succeed) ([(type-enum :dragon)] succeed))

Note that I removed a layer of square brackets.

0 votes
by
_Comment made by: joeosborn_

Nope! I actually want both. I'm doing some temporal logic stuff and I wanted some conveniences for "updating" a fluent, so I wanted to distinguish between the "key part" and the "value part" of the arguments. It looks silly for facts with no "value part", but it lets me write procedures and fns something like this:


(defrel heldo Time Fluent)
(defrel ¬heldo Time Fluent)
(declare fluent-obtainedo) ; most recent 'held' not terminated by a '¬held', or fail
(defn alter-fluent [Time Rel Key NewVal]
  ;todo: error check, ensure old != new, old obtains, new does not obtain
  (doseq [old-val (run* [old-val] (fluent-obtainedo Time [Rel Key old-val]))]
    (fact ¬heldo Time [Rel Key old-val]))
  (fact heldo Time [Rel Key NewVal]))
. . .
(fact heldo 0 ['pos [0] [0 0]])
. . .
(alter-fluent 1 'pos [0] [1 1])


And I write all the non-temporal fluents that way too for consistency and to help prevent mistakes.
0 votes
by

Comment made by: dnolen

I'll try to give a closer look at this issue over the weekend.

0 votes
by

Comment made by: dnolen

We're thinking about a more general solution here: http://github.com/clojure/core.logic/wiki/Better-syntax-support

0 votes
by
Reference: https://clojure.atlassian.net/browse/LOGIC-44 (reported by alex+import)
...