First post: how to mimic a Java List with types?

80 zobrazení
Přeskočit na první nepřečtenou zprávu

Jack Park

nepřečteno,
13. 8. 2020 21:24:4813.08.20
komu: Clojure
The problem:

In Java, I have an interface IInferrable which is basically  boolean eval();

Any Java object which extends IInferrable, no matter what it is, will answer to eval() and return a boolean.
The idea lies at the heart of an inference engine.

But, I also define a class AndList which implements IInferrable and extends java.util.ArrayList<Inferrable>

So, AndList, and its sibling OrList behave just like a List object, but also will answer to eval() by running the collection and dealing with what each element returns when it, too, is eval()'d.

I'd really love to discover how to pull that off in Clojure.

Many thanks in advance. -Jack

Alexandre Almosni

nepřečteno,
14. 8. 2020 1:52:0614.08.20
komu: clo...@googlegroups.com
Maybe your objects could be defined by a map containing functions and objects.

So exampleAndList = {:fn and, :coll [a b c]}


And then eval goes through your objects recursively, using

(def eval [o]
(apply (:fn o) (map eval (:coll o)))) 

- with a special case that if o is not a map but say a Boolean you just return itself



On 14 Aug 2020, at 02:24, Jack Park <jack...@topicquests.org> wrote:


--
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/3e2ffc86-0e4f-4c0c-92c3-58e0848d5ba7o%40googlegroups.com.

Oleksandr Shulgin

nepřečteno,
14. 8. 2020 3:05:5214.08.20
komu: clo...@googlegroups.com
On Fri, Aug 14, 2020 at 7:51 AM Alexandre Almosni <alexandr...@gmail.com> wrote:
Maybe your objects could be defined by a map containing functions and objects.

So exampleAndList = {:fn and, :coll [a b c]}


And then eval goes through your objects recursively, using

(def eval [o]
(apply (:fn o) (map eval (:coll o)))) 

- with a special case that if o is not a map but say a Boolean you just return itself

A slight problem with that approach is that clojure.core/and is not a function, but a macro: the reason being is that it stops evaluating forms as soon as it hits a falsey value (similar for "or").
A smart implementation of And/OrList would like to short-circuit as well I guess, so you'll need to express that somehow.

Of the clojure.core library that property is satisfied by transducers: https://clojure.org/reference/transducers#_early_termination

Regards,
--
Alex

Oleksandr Shulgin

nepřečteno,
14. 8. 2020 4:25:5014.08.20
komu: clo...@googlegroups.com
On Fri, Aug 14, 2020 at 9:05 AM Oleksandr Shulgin <oleksand...@zalando.de> wrote:
On Fri, Aug 14, 2020 at 7:51 AM Alexandre Almosni <alexandr...@gmail.com> wrote:
Maybe your objects could be defined by a map containing functions and objects.

So exampleAndList = {:fn and, :coll [a b c]}


And then eval goes through your objects recursively, using

(def eval [o]
(apply (:fn o) (map eval (:coll o)))) 

- with a special case that if o is not a map but say a Boolean you just return itself

A slight problem with that approach is that clojure.core/and is not a function, but a macro: the reason being is that it stops evaluating forms as soon as it hits a falsey value (similar for "or").
A smart implementation of And/OrList would like to short-circuit as well I guess, so you'll need to express that somehow.

Of the clojure.core library that property is satisfied by transducers: https://clojure.org/reference/transducers#_early_termination

I came up with the following:

;; and-transducer with early termination:
 (defn xand [rf]
   (fn
     ([] (rf))
     ([res] (rf res))
     ([res inp] (if inp
                  (rf res inp)
                  (reduced inp)))))

;; reducing function implementing and:
  (defn andr
   ([] true)
   ([i] i)
   ([r i] (and r i)))

;; noisy variant of get:
 (defn noisy-get
  ([k]
   (fn [m]
     (noisy-get m k)))
  ([m k]
   (println "noisy-get" m k)
   (get m k)))

;; all in action:
user> (def xf (comp (map (noisy-get :data))
                    xand))
#'user/xf
user> (transduce xf andr [{:data 1} {:data false} {:no-data 3}])
noisy-get {:data 1} :data
noisy-get {:data false} :data
false
user> (transduce xf andr [{:data 1} {:data 2} {:no-data 3} {:data 4}])
noisy-get {:data 1} :data
noisy-get {:data 2} :data
noisy-get {:no-data 3} :data
nil
user> (transduce xf andr [{:data 1} {:data 2} {:data 3} {:data 4}])
noisy-get {:data 1} :data
noisy-get {:data 2} :data
noisy-get {:data 3} :data
noisy-get {:data 4} :data
4

It does feel a bit redundant to have the reducing function on top of a transducer that accomplishes mostly the same, but I haven't found a way to make it shorter.

Cheers,

Jesús Gómez

nepřečteno,
14. 8. 2020 6:41:3214.08.20
komu: clo...@googlegroups.com
Why not to Java-Interop with that Interface and those Classes directly, the same way you use them in Java?

--

Jack Park

nepřečteno,
14. 8. 2020 14:35:0314.08.20
komu: clo...@googlegroups.com
Mapping and transducers seems appropriate, though I'm still wrapping my head around how to make this work.
From the "class with functions" mindset, I need an ArrayList into which I can:
a) add members to the list from time to time
b) run eval() on it; a conjunctive list will exit false on first false firing of any member, or default exit true otherwise; a disjunctive list will exit true on first true firing of a member, and default false.

When I read the code here, I am not yet clear (sorry, it's  on me, not the code) how to make such a "class-like" object.

I am still exploring this avenue, as well as looking at java-interop or something about a proxy.

Many thanks.
Jack

--
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.

Jack Park

nepřečteno,
14. 8. 2020 14:44:4414.08.20
komu: clo...@googlegroups.com
This idea shows up early in Clojure text books. This concept comes to mind rather quickly:

(def and_list (ArrayList.)
   ...
)

But, in the end it is an ArrayList which has an interface-defined behavior, e.g. boolean eval(); (from Java)
Thus far, in a tiny VSCode project, I defined

(definterface IEvaluable
   (^boolean runIt []))

and defined a simple object which will return false and one which will return true. I'll use those to populate a conjunctive and a disjunctive list.

Now I must define a list object which runs that interface.

Still digging and experimenting, but, on the surface, this approach appears to be possible.

From above, as a sketch:

(def and_list (ArrayList.)
  IEvaluable
  (runIt [this]
      <some code to conditionally walk the list>))
)

which is now on the table to explore.

Many thanks
Jack

Sam Hahn

nepřečteno,
14. 8. 2020 16:11:0514.08.20
komu: Jack Park, clo...@googlegroups.com
Jack - I'll call you :) 
Cheers - Sam

-------- Original Message --------
Subject: Re: First post: how to mimic a Java List with types?
From: Jack Park <jack...@topicquests.org>
Date: Fri, August 14, 2020 11:34 am
To: clo...@googlegroups.com

Mapping and transducers seems appropriate, though I'm still wrapping my head around how to make this work.
From the "class with functions" mindset, I need an ArrayList into which I can:
a) add members to the list from time to time
b) run eval() on it; a conjunctive list will exit false on first false firing of any member, or default exit true otherwise; a disjunctive list will exit true on first true firing of a member, and default false.

When I read the code here, I am not yet clear (sorry, it's  on me, not the code) how to make such a "class-like" object.

I am still exploring this avenue, as well as looking at java-interop or something about a proxy.

Many thanks.
Jack


The problem:

In Java, I have an interface IInferrable which is basically  boolean eval();

Any Java object which extends IInferrable, no matter what it is, will answer to eval() and return a boolean.
The idea lies at the heart of an inference engine.

But, I also define a class AndList which implements IInferrable and extends java.util.ArrayList<Inferrable>

So, AndList, and its sibling OrList behave just like a List object, but also will answer to eval() by running the collection and dealing with what each element returns when it, too, is eval()'d.

I'd really love to discover how to pull that off in Clojure.
--
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.
--
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.

Oleksandr Shulgin

nepřečteno,
14. 8. 2020 16:38:2414.08.20
komu: clo...@googlegroups.com
On Fri, Aug 14, 2020 at 8:44 PM Jack Park <jack...@topicquests.org> wrote:
This idea shows up early in Clojure text books. This concept comes to mind rather quickly:

(def and_list (ArrayList.)
   ...
)

But, in the end it is an ArrayList which has an interface-defined behavior, e.g. boolean eval(); (from Java)
Thus far, in a tiny VSCode project, I defined

(definterface IEvaluable
   (^boolean runIt []))

and defined a simple object which will return false and one which will return true. I'll use those to populate a conjunctive and a disjunctive list.

Now I must define a list object which runs that interface.

Still digging and experimenting, but, on the surface, this approach appears to be possible.

From above, as a sketch:

(def and_list (ArrayList.)
  IEvaluable
  (runIt [this]
      <some code to conditionally walk the list>))
)

which is now on the table to explore.

Nevermind transducers: I've just realized that reduced can be used with the normal reduce.  E.g. here's short-circuiting AND-reduction fn:

(defn andr
  ([] true)
  ([i] i)
  ([r i] (let [o (and r i)]
           (if o
             o
             (reduced o)))))

When it comes to the actual lists, it depends how you'd like to represent them.  E.g. I could imagine something like the following can be a useful notation:

[:and 1 2 [:or 3 4] 5]

or even more direct:

(quote (and 1 2 (or 3 4) 5))

If you really want an interface-like look and feel, then protocols might be the right answer:

(defprotocol Evaluable
  (evaluate [this]))

(defrecord AndList [items]
  Evaluable
  (evaluate [this]
    (reduce andr (:items this))))

user> (evaluate (->AndList [1 2 3]))
3
user> (evaluate (->AndList [1 false 3]))
false

To complete it, you'll need to add the OrList and sneak (map evaluate) in the reduce call in both And- and OrList.

Cheers,
--
Alex

Jack Park

nepřečteno,
14. 8. 2020 16:48:2314.08.20
komu: clo...@googlegroups.com
Wow!

I say that because that might be getting closer to what I have in mind but still not there yet.
I'm really trying to develop a type which honors two interfaces, one for the list object, and one for the IEvaluable object.

As a type, I invoke it, then add objects to it, then place it, as an IEvaluable object inside another such collection. Perhaps it is the first - root - which begins an and/or network of IEvaluable objects, some of which are the workers which go outside and make computations before returning their boolean.

But, I have been wandering down another rabbit hole, the one where I ignore list comprehensions and proxy an ArrayList.

I have code which almost runs, but which is far from perfect.
I created a gist which exposes my current experiment - which is close, but no cigars yet.


Thanks
Jack

--
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.

Jack Park

nepřečteno,
14. 8. 2020 17:27:1914.08.20
komu: clo...@googlegroups.com
Alex,

I plan to explore this idea.
Many thanks!

Jack

On Fri, Aug 14, 2020 at 1:38 PM Oleksandr Shulgin <oleksand...@zalando.de> wrote:
<snip.>

<snip>

matthew...@gmail.com

nepřečteno,
14. 8. 2020 20:04:1714.08.20
komu: Clojure

Another option would be to do what Alex is suggesting and define and as a function. Just because it’s a macro in clojure.core doesn’t mean you can’t write your own :)

(defn eval' [x]
  (if (map? x)
    (apply (:fn x) (:coll x))
    x))

(defn and-list [& items]
  (let [if? (fn [x] (if x true false))
        and' (fn [& args] (every? if? args))]
    {:fn and' :coll items}))

(eval' true) ;=> true
(eval' false) ;=> false
(eval' (and-list 1 2 3)) ;=> true
(eval' (and-list 1 2 3 false)) ;=> false

Ditto with or.

Erik Assum

nepřečteno,
15. 8. 2020 3:14:3315.08.20
komu: clo...@googlegroups.com
Why not tease things apart?

(defn eval [x] ...)

(some eval my-list) ;; or list
(every? eval my-list) ;; and list

https://clojuredocs.org/clojure.core/every_q

Now, you can make the eval function polymorphic in several ways, simplest is to case or cond on some property of x. You could also use multimethods or a protocol to achieve this. 


Erik. 
-- 
i farta

15. aug. 2020 kl. 02:04 skrev matthew...@gmail.com <matthew...@gmail.com>:


--
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.

Jack Park

nepřečteno,
15. 8. 2020 15:42:4215.08.20
komu: clo...@googlegroups.com
Hi Erik,

I think that teasing things apart helps along some dimensions, but the problem I face, still thinking like a Java hacker, is that I need to put things together:

I need to construct a type  (deftype) which honors two different interfaces, a list, and an evaluable object. I need the type because I want to create it, then add members to the list, and then one of:
a) insert the object into another one (building and/or trees)
b) evaluate it - when it is the root of the tree.

Members in such lists can be either other evaluable lists, or they can be evaluable objects which perform computations - even with side effects - but which return a boolean.

The several ideas posted in this thread are greatly helping me to see through this situation with fresh eyes, but, thus far, nothing has convinced me to drop the goal of creating a type which is a list and evaluable, into which I can insert conjunction or disjunction evaluators.

Many thanks,
Jack

Brandon R

nepřečteno,
15. 8. 2020 17:28:1015.08.20
komu: clo...@googlegroups.com
Hey Jack,

Just been a fly on the wall for this convo, and aside from offering a specific solution, which others have done, I'm pretty certain this can be done with just protocols and records. Make a record (like your type) that implements the two protocols (like your interfaces). You could achieve your desired functionality without records and protocols but what you describe sounds like a fitting case for them.


Members in such lists can be either other evaluable lists, or they can be evaluable objects which perform computations

A simple Clojure list/vector can hold any type of data you put into it. So your record can hold a vector of these things, which can be represented as Clojure data structure (lists / maps / etc).

You say you need to construct a type, but I urge you to challenge this assumption, again and again if needed. I think it's natural to think you need a type because of coming from Java/OOP land, especially if you were there a long time. Old patterns die hard?

I could be wrong though. :)


My 2 cents,
Brandon

Jack Park

nepřečteno,
15. 8. 2020 19:19:1615.08.20
komu: clo...@googlegroups.com
Brandon!

That just might be the idea I needed. Thank you.

Film at 11...

Jack Park

nepřečteno,
15. 8. 2020 22:58:3315.08.20
komu: clo...@googlegroups.com
My first attempt at using records (showing my ignorance here) is found in this gist

It fails with this error (which doesn't give very good google)

Syntax error macroexpanding clojure.core/defn at (cljtest/core.clj:123:1).
  - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
  - failed: (or (nil? %) (sequential? %)) at: [:fn-tail :arity-n :bodies] spec: :clojure.core.specs.alpha/params+body

I wonder what I am missing.
Thanks in advance for ideas.

-Jack

Jack Park

nepřečteno,
16. 8. 2020 22:27:1216.08.20
komu: clo...@googlegroups.com
Brandon,

Just a quick note to say that I conquered ConjunctiveList and have DisjunctiveList coded.
After further tests to validate what it's doing, I'll post a gist to show the working code.

You were entirely right: using definterface and defrecord, I can construct these structures. Still a few tests to run.

Many thanks
Jack

On Sat, Aug 15, 2020 at 2:28 PM Brandon R <brando...@gmail.com> wrote:
Odpovědět všem
Odpověď autorovi
Přeposlat
0 nových zpráv