Strange Vector failure

85 views
Skip to first unread message

Jack Park

unread,
Jul 18, 2021, 1:41:22 PMJul 18
to Clojure
I have a class which treats a sequence as a conjunctive list of objects which, when evaluated, return a boolean.  It is an attempt to use doseq to walk along that list, evaluating each entry, and anding that result with  boolean atom. It fails. A sketch of the code is this - taken from the error message:

inside (defn AndList...

(reify
    ie4clj.api.Inferrable
    (defn evalMembers
        [members]
        (defn result (atom true))
        (doseq [x members]
            (result = (and result (eval x))))
        (println (clojure.core/deref result))
    (result))) - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list

It could be that my Java background is clouding my use of clojure. Any comments will be appreciated.

Thanks
Jack

Cora Sutton

unread,
Jul 18, 2021, 2:23:15 PMJul 18
to clo...@googlegroups.com
Hi Jack!

I could be wrong but I think this could just be: (every? eval members)

I see a few things here that seem strange to me so I wanted to share a few points that might be helpful (or might not, let me know either way) for future code.

* So typically you don't want to def or defn within another function call since that will define a new value at the top level.

(defn foo []
  (def bar 1)
  (println (inc bar))

(foo)
;; ^^ calling foo will define bar at the top level

bar
;; => 1
;; whoops, didn't mean to have that at the top level like that
;; imagine if two different threads called that in parallel ::grimace::


Instead, you usually want to use the let function: https://clojuredocs.org/clojure.core/let

So in your code you might use this something like:

(let [result (atom true)]
  ....)


The error you're seeing is from the (defn result ...) in your code, you're missing the argument vector [] after result -- so it would look like (defn result [] (atom true)) -- but you really don't want to defn like that, I think.

* To update an atom's value you don't want to assign like that, you want to use swap! https://clojuredocs.org/clojure.core/swap!

(swap! f
       (fn [cur-val new-val] (and cur-val new-val))
       (eval member))


* You probably don't want to use an atom here. Atoms are usually for data that you intend to have multiple threads accessing. In this case it's just a value that changes during a single thread's execution here.

How else could you solve this if not for the very convenient every? function? There are a bunch of ways! Here are a few, with things written out pretty explicitly so they're more clear.

loop/recur:

(loop [result true
       remaining-members members]
  (let [member (first remaining-members)
        remaining-members (rest members)
        new-result (eval member)]
    (if new-result
      (recur true remaining-members)
      false)))

 
reduce v1:

(reduce (fn [result member]
          (and result
               (eval member)))
        true
        members)


reduce v2.0, that will now stop iterating once one of the members evals to false:

(reduce (fn [_ member]
          (or (eval member)
              (reduced false)))
        true
        members)

My point with sharing these is that in clojure usually the best way to solve these problems is to pass new values to the next iteration while accumulating a result instead of changing a variable on each iteration. Or to use one of these sweet built-in functions.

Does that make sense?

* I thiiiiiiink you might not mean eval but I'm interested in what kind of problem you're solving! :)

Hope that helps!
Cora

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/clojure/f67cfcd0-8e1e-4780-bc00-f6993979e7afn%40googlegroups.com.

Jack Park

unread,
Jul 18, 2021, 3:14:27 PMJul 18
to clo...@googlegroups.com
Thank you, Cora. That's awesome, and it opens new issues for me. Here is my revised code from your first level comment
(defn AndList
  [members]
  (reify
    ie4clj.api.Inferrable
    (every? evalSelf members)

    ))
Which opens new issues:
  • Right at "(reify" I get this error message
Syntax error (IllegalArgumentException) compiling at (ie4clj/AndList.clj:8:3).
Don't know how to create ISeq from: clojure.lang.Symbol
It's as if the ability to reify an interface is not recognized
Notice there is an interface call which, itself, is not :require'd evalSelf, which leads to
  • evalSelf is not recognized
That one is beginning to suggest to me that I should, instead, not use an interface, but just write code which can use clojure's eval.
But, I've been creating classes, not functions. If I modify my code such that the members sequence contains functions, then perhaps that would work.

I confess, I'm saddled with a deep java experience.
Your coaching is extremely helpful.
Many thanks
Jack

James Reeves

unread,
Jul 18, 2021, 3:26:54 PMJul 18
to 'EuAndreh' via Clojure
You're missing the method name and arguments now. It should be:

(reify
   INTERFACE
   (METHOD [ARGUMENTS]
     CODE))

James Reeves

unread,
Jul 18, 2021, 3:26:54 PMJul 18
to 'EuAndreh' via Clojure
You have a "defn" there by mistake.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

--
James Reeves


Cora Sutton

unread,
Jul 18, 2021, 3:34:21 PMJul 18
to clo...@googlegroups.com
No worries! Deep Java experience is such a huge asset when it comes to Clojure, there's nothing to be ashamed of!

So, for reify, if I understand what you're attempting, I think you'd have something like:

(reify
  ie4clj.api.Inferrable
  (evalMembers [members]
    (every? evalSelf members))))


Tha is, if evalSelf is really what you want there.

But, Jack, and I mean this in the kindest way possible, do you think it's possible we have an XY situation going on here? https://xyproblem.info/

It's possible we're helping you solve the wrong thing and I'd love to be sure we're pointing you down the best path possible. So, can I ask what problem you're solving? Can you share more code somewhere, maybe in a GitHub gist?

I just worry that maybe you're solving a problem the "Java way" when there's an easier way to go about it in the "Clojure way".

Hope this is helpful!
Cora


Cora Sutton

unread,
Jul 18, 2021, 3:38:05 PMJul 18
to clo...@googlegroups.com
And for what it's worth, I haaaate how condescending that site (https://xyproblem.info/) is. It could be so much kinder.

Cora Sutton

unread,
Jul 18, 2021, 3:48:00 PMJul 18
to clo...@googlegroups.com
Oh! I just saw your post from earlier and Alex's response, I strongly believe we have an XY problem here. In Clojure you most likely wouldn't use interfaces like this. We can move this discussion over there since Alex has kicked it off.

Jack Park

unread,
Jul 18, 2021, 4:58:07 PMJul 18
to clo...@googlegroups.com
Cora and Alex,

I think you are both hitting some nails on their heads.
Yes, the java mentality creates a kind of flow and patterns which clearly do not apply to clojure (eliding jokes like "what must they be smoking").

In java, I can make a list structure and do whatever I want with it - because it's not immutable. Now that we're not in Kansas anymore, I must get over relying on changing variable objects at will, but then, declaring atoms seems to have its own issues. So, try to avoid those. "Let" declares its own context but it might be creating a result you want to return in the outer context - that crops up from time to time.

So, where am I now?
I really want to build a set of classes which, in a weak sense (the java way) of extending sequence to give it new behaviors.
But, the lesson today is that I am trying to build a class which evals as if it is a method, so I must ditch those classes and migrate those behaviors to a collection of functions.

That's the work in progress, and it includes ditching the api interface altogether.

That's work in progress (painful but fun). I'll report back.
It's strange - to me - that the issues I face are non-issues for others here, mostly because of my deep java biases.

Thanks
Jack

Jack Park

unread,
Jul 18, 2021, 7:21:29 PMJul 18
to clo...@googlegroups.com
(every? eval members)  does not appear to work on a list of functions designed to evaluate to a boolean.

That code is used in a function evaluateAnd

Two simple tests
(evaluateAnd [true true] --> true
(evaluateAnd [true false] --> nil (why not "false" as the every? examples show?)

The specific code for building the list of functions is this

(def x (atom []))
  (let [result (list (ref SimpleTrue) (ref SimpleFalse))]
    (println "BAL1" result )
    (reset! x result)
    )
  (println "BAL2" @x )

  (@x) <<<< returns the atom's value

And the final println is this

BAL2 (#object[clojure.lang.Ref 0x335b5620 {:status :ready, :val #object[ie4clj.Tests$SimpleTrue 0x6eb2384f ie4clj.Tests$SimpleTrue@6eb2384f]}] #object[clojure.lang.Ref 0x3c9c0d96 {:status :ready, :val #object[ie4clj.Tests$SimpleFalse 0x31dadd46 ie4clj.Tests$SimpleFalse@31dadd46]}])

evaluateAnd never saw the result, with this error message

clojure.lang.PersistentList cannot be cast to clojure.lang.IFn

The test which fails is this

 (def result (evaluateAnd  (buildAndList) ))  <<< fails here
  (println "bar" result)
  (result)

The googleverse seems to agree that there are extra parens around the value. Google isn't giving me an obvious way to take that value outside of its surrounding parens (bal2 above).
Still looking, and hoping that solves the problem.
Maybe there's a way to go back to buildAndList and not return the value with parens.

On Sun, Jul 18, 2021 at 11:23 AM Cora Sutton <co...@sutton.me> wrote:

Cora Sutton

unread,
Jul 18, 2021, 8:00:16 PMJul 18
to clo...@googlegroups.com
Hello again Jack,

On Sun, Jul 18, 2021 at 6:21 PM Jack Park <jack...@topicquests.org> wrote:
(every? eval members)  does not appear to work on a list of functions designed to evaluate to a boolean.

If members is a list of functions then you would do:

(every? (fn [member] (member)) members)

Showing it work here:

(every? (fn [member] (member)) [(constantly true) (constantly true)])
;; => true
(every? (fn [member] (member)) [(constantly true) (constantly false)])
;; => false

 
That code is used in a function evaluateAnd

Two simple tests
(evaluateAnd [true true] --> true
(evaluateAnd [true false] --> nil (why not "false" as the every? examples show?)

In Clojure things are either "truthy" or "falsey", and the only "false" values are false and nil so returning nil is usually fine. Everything else is "truthy". I wouldn't worry about it returning nil since other things were broken anyways.

 
The specific code for building the list of functions is this

(def x (atom []))
  (let [result (list (ref SimpleTrue) (ref SimpleFalse))]
    (println "BAL1" result )
    (reset! x result)
    )
  (println "BAL2" @x )

  (@x) <<<< returns the atom's value

And the final println is this

BAL2 (#object[clojure.lang.Ref 0x335b5620 {:status :ready, :val #object[ie4clj.Tests$SimpleTrue 0x6eb2384f ie4clj.Tests$SimpleTrue@6eb2384f]}] #object[clojure.lang.Ref 0x3c9c0d96 {:status :ready, :val #object[ie4clj.Tests$SimpleFalse 0x31dadd46 ie4clj.Tests$SimpleFalse@31dadd46]}])

evaluateAnd never saw the result, with this error message

clojure.lang.PersistentList cannot be cast to clojure.lang.IFn

Refs are the wrong thing to use here. In fact I'd stay away from atoms and refs unless you have multiple threads that need to mutate the same values. They're just confusing things now, I think.
 

The test which fails is this

 (def result (evaluateAnd  (buildAndList) ))  <<< fails here
  (println "bar" result)
  (result)

The googleverse seems to agree that there are extra parens around the value. Google isn't giving me an obvious way to take that value outside of its surrounding parens (bal2 above).
Still looking, and hoping that solves the problem.
Maybe there's a way to go back to buildAndList and not return the value with parens.

I think a key thing to explain is that in Clojure generally you're not making new types of collections. There's this famous-ish saying that Clojure holds to pretty well:

"It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures."
- Alan Perlis

Most functions in the Clojure world operate on a handful of basic data types and structures. This makes it really easy to chain and combine functions to slice and dice data since you don't need to convert between types.

I don't think I've ever made a special collection type in Clojure, it's not all that common. So I'd suggest that while you're at this point in your journey you try to stick to the built-in Clojure collection types and use the built-in functions to operate on them.

To give you a little direction, instead of a Person object you could make a hashmap like {:first-name "Jack" :last-name "Park"} and pass that around. And then you can make a function that operates on that.

(defn full-name
  [person]
  (str (get person :first-name) " " (get person :last-name)))

 
And then you could expand that to maybe {:first-name "Jack" :last-name "Park" :people-talked-to-on-mailing-list ["Cora Sutton"]} and then operate on a collection of people like:

(defn people-talked-to-on-mailing-list
  [person all-people]
  (let [people-to-find (set (get person :people-talked-to-on-mailing-list))]
    (filter (fn [p]
              (people-to-find (full-name p))
            all-people))


(people-talked-to-on-mailing-list jack all-people)
;; => {:first-name "Cora" :last-name "Sutton" :people-talked-to-on-mailing-list ["Jack Park"]}




Jack Park

unread,
Jul 18, 2021, 8:19:06 PMJul 18
to clo...@googlegroups.com
Cora!

I made those changes. It is still working to the degree it was, with the same error
clojure.lang.LazySeq cannot be cast to clojure.lang.IFn


which, according to the intertubes, means that my buildAndList returns (value) instead of value. I tried flatten. No cigar.

Thanks
Jack

Tanya Moldovan

unread,
Jul 18, 2021, 11:01:05 PMJul 18
to clo...@googlegroups.com
Hey, 

Could you share the code you have now? 

Cora Sutton

unread,
Jul 18, 2021, 11:13:58 PMJul 18
to clo...@googlegroups.com
Code would be helpful for sure! Also, it might be time to move this to Clojurians Slack http://clojurians.net/

There is a #beginners channel where volunteers are available to help in a more synchronous fashion which might help get to the bottom of this a bit quicker.

Jack Park

unread,
Jul 18, 2021, 11:14:34 PMJul 18
to clo...@googlegroups.com
Tanya,

Here is a gist with the three files being hacked at the moment; the test file is in a different place than I've sketched above.
You will have to remove a couple of :remove lines from test because I didn't include that code - it's not in play yet.

Hope that helps.
The program is just a simple list-based inference engine which allows you to build rule trees from conjunctive and disjunctive lists, members of which can be any object which can eval to return a boolean, which happens to include the two list forms; it includes "not" which can take another such object.

Jack Park

unread,
Jul 18, 2021, 11:16:56 PMJul 18
to clo...@googlegroups.com
Cora, I agree. A gist was just submitted here. I'm in the clojurians slack; I have a weak memory of being there once before. But, happy to put a summary of this in #beginners as you suggest.



Tanya Moldovan

unread,
Jul 19, 2021, 3:36:25 AMJul 19
to clo...@googlegroups.com
I think you forgot to link it, but I think I found it  :)

You don't really need line 11. It will run without it.
Don't use defs inside a function. Use let. Always.
Don't use atoms inside a function. In fact, unless you need some shared state between processes - just don't use them. Same goes for refs.

(I changed the name of function to be more clojure like (ie: SimpleTrue -> simple-true and so on)

It's not clear what you want to do with the SimpleTrue, SimpleFalse. Do you need it to be a list? Or a boolean value?
If boolean - then do this:
(defn simple-true [] true)
(defn simple-false [] false)
(defn not-simple-true [] (not simple-true))
(defn not-simple-false [] (not simple-false))
If you need a list, just do:
(defn simple-true [] '(true)) ;<--- notice the quote before the parenthesis
(defn simple-true [] [true]) ; or just use vectors
In clojure, if you need lists, always put a quote before the parenthesis, otherwise it will be interpreted as a function.
So (true) will throw an exception, but '(true) will work:
ie4clj.core=> (true)
Execution error (ClassCastException) at ie4clj.core/eval1490 (form-init3173779095201492457.clj:1).
java.lang.Boolean cannot be cast to clojure.lang.IFn

ie4clj.core=> '(true)
(true)

Another place where it will throw an error is line 41
(@x)
Do you want to return the result as a list or just return the result?
If you want a list you do this:
'(@x) ; <-- notice the quote
[@x] ; or use vector
If you want to return a result just do this: 
@x
Also, don't use atoms ) 

On line 37 you are assigning the result of let to that atom, BAL2 will be the same as BAL1, so you can just skip it and return the result from let.
Like this:
(defn build-and-list []
  (println "BAL")
  (let [result (flatten (list simple-true simple-false))] ;<-- actually not sure if this is the thing you want here. This will be a list of functions.
    (println "BAL1" result )
    result))

Another thing is with the test function. Don't use defs there, just use a let clause, like this:
(defn first-test []
  (println "First Test")
  (let [x (evaluate-and [(constantly true) (constantly true)])
        y (evaluate-and [(constantly true) (constantly false)])
        l (build-and-list)
        result (evaluate-and l)]
    (println "A" x)
    (println "B" y)
    (println "C" l)
    (println "bar" result)
    result))
And here is the evaluate-and function with let instead of def.
(defn evaluate-and
  [members]
  (println "EA" members)
  (let [result (every?  (fn [member] (member)) members)]
    (println "EA+" result)
    result)) ;<-- I added this line, as (println) one return nil, and I thought you needed the result true or false (?)
This compiles and runs for me (unless I forgot something). (if it doesn't just tell me, I'll commit the code so you can play with it)


Jack Park

unread,
Jul 19, 2021, 3:26:50 PMJul 19
to clo...@googlegroups.com
So, that did the trick. No more defs or atoms, a few tweeks, and it ran. naming conventions brought into line with clojure.

Next step is to prove it can return a false, and then continue adding features.

Many thanks to all, and particularly to Tanya for reviewing my code.

Jack Park

unread,
Jul 19, 2021, 3:58:08 PMJul 19
to clo...@googlegroups.com
Cora

(every? (fn [member] (member)) members)
works fine on [constantly true & false
but fails with
java.lang.Boolean cannot be cast to clojure.lang.IFn
on the lists I construct.

In truth, I thought all the code was working, but that turned out ot be an artifact of the test I designed. When I changed the test conditions, evaluate_and failed.


On Sun, Jul 18, 2021 at 5:00 PM Cora Sutton <co...@sutton.me> wrote:

Cora Sutton

unread,
Jul 19, 2021, 6:33:39 PMJul 19
to clo...@googlegroups.com
Your members list needs to be filled with things that can be called as functions, since that's what that code snippet does, and booleans definitely cannot be called as functions. That's what the error means, there's a boolean in your list and it's trying to cast it to an IFn (a Clojure function interface) when it is called as (member).

Can you show the lists you construct? Are they full of functions that take no arguments? Do you want the lists to be able to contain booleans too?

Jack Park

unread,
Jul 19, 2021, 6:41:57 PMJul 19
to clo...@googlegroups.com
Great points!
They are filled with functions which look like this

(defn simple_true [] (true))

They are not booleans but functions which return a boolean.
Here is a list of two of those as produced by the code:

(#object[ie4clj.Tests$simple_false 0x3a4621bd ie4clj.Tests$simple_false@3a4621bd]
 #object[ie4clj.Tests$simple_false 0x3a4621bd ie4clj.Tests$simple_false@3a4621bd])

Or maybe I missed something.

Cora Sutton

unread,
Jul 19, 2021, 6:43:57 PMJul 19
to clo...@googlegroups.com
Those are functions that call booleans as functions. Try this:

(defn simple-true [] true)

Jack Park

unread,
Jul 19, 2021, 6:55:08 PMJul 19
to clo...@googlegroups.com
Did. That suggestion was made earlier. Did not change anything.

Here's a test which ran just fine
(def x (evaluate_and (list true true)))
  (println "A" x)
  (def y (evaluate_and (list true false)))
  (println "B" y)

But, the moment I attempt to make a list with two functions in it, the code breaks and returns - without any errors - not a boolean, but the structure I passed it.


Cora Sutton

unread,
Jul 19, 2021, 9:04:04 PMJul 19
to clo...@googlegroups.com
Hello again, Jack. I'm not sure what your code looked like before or looks like now but I think maybe a different way of helping you out with this is in order. Here's some code that does what I think you're going for and runs:


Have a look, play with it a bit, change around value and see what breaks. Hope that's helpful!

Jack Park

unread,
Jul 20, 2021, 8:32:16 PMJul 20
to clo...@googlegroups.com
Hi Cora,

I got dragged away but plan to study your contribution, which I deeply appreciate.

My project plans to be a very simple clojure re-implementation of an inference engine I wrote in Forth more than 20 years ago, a kind of list processor in which you have an interface which advertises a simple "eval"-like method, applicable to structures as well as atoms. Thus, conjunctive and disjunctive lists, plus not, all of which can be populated with combinations of those and atoms, tiny computational objects which do stuff within the same interface. Atoms can ask questions at the UI, do computations on databases, and so forth.

I benchmarked that against a more traditional symbolic (frame-based) inference I had written to control autoclaves for curing polymer resins - a contract for the military which ended up helping to fix fighter jets which were grounded because of bird impacts penetrating composite leading edges and crashing - we were able to cure improved leading edges; the new code executed more than 10 times faster than the old symbolic code. That was then (Forth), this is now (Java/Clojure). I've done it in Java. Now it's time to use that as a learning exercise.

Many thanks
Jack

Jack Park

unread,
Jul 23, 2021, 3:32:42 PMJul 23
to clo...@googlegroups.com
Ok. I got back to this, now running Cora's gist. It gives me a different place to explore these issues.
More soon.
Jack

Jack Park

unread,
Jul 23, 2021, 8:22:47 PMJul 23
to clo...@googlegroups.com
Hello again, Cora (and list!)

I have your gist running, then added a new feature


The first code was for conjunctive lists, I added disjunctive lists

There, I started with some? but could not make the grade, ended up with some fn where fn is eval. That's the code.
It's behaving strangely, but maybe I'm on the right track.

Where this is going is that a list can be populated with things other than simple functions like SimpleTrue; can be populated with conjunctive and disjunctive lists, each of which can be similarly populated. That, of course, means that evaluating a single inferrable list is the same as walking a possibly complex (no loops, hopefully) spider web.

Thanks
Jack

Cora Sutton

unread,
Jul 23, 2021, 9:03:23 PMJul 23
to clo...@googlegroups.com
You can stay away from eval unless you have extremely special needs, really. I never use it myself. The evaluate-or-fns and evaluate-and-fns don't care what the function is, it could be another call to evaluate-or-fns or evaluate-and-fns, and in this way you can recurse as deeply as you desire and only evaluate when you actually want values out of it.

(defn evaluate-and-fns
  "Returns true if every function in members returns a value that is true-ish according to Clojure's
  truthiness rules. Otherwise returns false."
  [members]
  (every? (fn [member]
            (member))
          members))

(defn evaluate-or-fns
  "Returns true if any function in members returns a value that is true-ish according to Clojure's
  truthiness rules. Otherwise returns false."
  [members]
  (boolean
   (some (fn [member]
           (member))
         members)))

(evaluate-or-fns [(fn []
                    (evaluate-and-fns [(fn [] (evaluate-and-fns [simple-true-fn simple-true-fn]))
                                       simple-true-fn]))
                  (fn []
                    (evaluate-and-fns [simple-true-fn simple-false-fn]))])


Jack Park

unread,
Jul 23, 2021, 10:54:54 PMJul 23
to clo...@googlegroups.com
Cora,

That's simply amazing. I added one more "or" test: all-false.

I confess, I'm not yet at the place where I look at that pattern and recognize it, but, thanks  to you and to the other hints I received here, I now have something to work with.

Next up for me will be to test the "not" functions.

Many thanks!

-Jack

Cora Sutton

unread,
Jul 24, 2021, 3:03:30 AMJul 24
to clo...@googlegroups.com
not, in clojure, is itself a function, so it would just be wrapping a other function call in (not (my-fn)). there is no limit to the recursion here, you can have functions in functions in functions

Jack Park

unread,
Jul 24, 2021, 9:39:31 AMJul 24
to clo...@googlegroups.com
That's precisely how I coded it. Just need to run some tests, then on to more complex testing with nested lists and so forth.

Thanks!

Reply all
Reply to author
Forward
0 new messages