-
Notifications
You must be signed in to change notification settings - Fork 2
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
feat: new IChoiceMap interface, implementations (2/n) #52
Conversation
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #52 +/- ##
==========================================
+ Coverage 68.58% 71.88% +3.30%
==========================================
Files 19 20 +1
Lines 748 996 +248
Branches 17 24 +7
==========================================
+ Hits 513 716 +203
- Misses 218 256 +38
- Partials 17 24 +7 ☔ View full report in Codecov by Sentry. |
fa56db4
to
39c82af
Compare
70fbc5e
to
40d408f
Compare
39c82af
to
11113f9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sritchie this looks great!
I left a number of small comments, but the overall structure looks great to me.
I would not fully trust my review on the parts of this code which provide implementations of Clojure/Clojurescript interfaces for choicemaps, since I am not familiar with those interfaces.
What is going to happen to dynamic/choice_map.cljc
after this file is added?
([m] (-get-value m)) | ||
([m k] (-get-value (get-submap m k)))) | ||
|
||
;; ## Choice |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice - I like the rebrand of ValueChoiceMap to Choice.
arr/IArray | ||
(to-array [_] [v]) | ||
(-from-array [_ xs idx] | ||
[1 (Choice. (nth xs idx))]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am probably misunderstanding the syntax here - but is this creating a vector [1, Choice(...)]?
If so, why is 1 the first element?
(If this is a syntax misunderstanding, pointers to a search term where I can understand this syntax? Thanks!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keep questions like this coming! also we should get a REPL up and pair soon.
This function wants a return value of a 2-element vector with
- how many items you took off of the input array
xs
- the new value you created with those elements
As described here https://github.com/InferenceQL/gen.clj/blob/main/src/gen/array.cljc#L10
So for Choice
, we take 1 thing off from xs
at start-idx
, and create the Choice
instance in the second slot.
(And yes you're right that it's creating the vector [1, Choice(,,,,)]
(-from-array [_ xs idx] | ||
[1 (Choice. (nth xs idx))]) | ||
|
||
#?@(:clj |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just FYI - from here to the defn of Empty is beyond my current Clojure knowledge to understand (so I have not reviewed this chunk).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is surprisingly annoying in Clojure to extend the base interfaces!! these are flexed in the tests, and I tried to match the implementations in Gen.jl for things like getindex
. I'll comment below on what these do
|
||
(declare kv->choicemap) | ||
|
||
(deftype EmptyChoiceMap [m] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sritchie why does this accept an argument? (My guess: is it impossible to define types with no data in side in Clojure?)
|
||
(declare equiv choicemap) | ||
|
||
(deftype DynamicChoiceMap [m] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder about the name DynamicChoiceMap here.
I think of the name DynamicChoiceMap in Gen.jl as stemming from the distinction with the StaticChoiceMap. Due to the way staging works in Gen.jl, the address shape of a StaticChoiceMap is known at compile time. Thus, methods for updating traces can be specialized to different StaticChoiceMap shapes at compile time. While the shape of a StaticChoiceMap is known "statically" (at compile time), the shape of a DynamicChoiceMap is "dynamic" (it can be modified at runtime). Thus, update methods which accept a DynamicChoiceMap cannot be specialized at compile-time to the shape of the update choicemap.
Is there going to be a similar distinction between dynamic choicemaps and static choicemaps in gen.clj?
Not totally sure what a better alternative name would be if not, but here are a few ideas... Maybe HierarchicalChoiceMap
or DictChoiceMap
or GeneralChoiceMap
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another possibility is to rename
ChoiceMap
-> Choices
DynamicChoiceMap
-> ChoiceMap
VectorChoiceMap
-> ChoiceVector
and keep Choice
.
This sort of implies VectorChoiceMaps don't have hierarchical structure, though.
So maybe the best bet is just to change DynamicChoiceMap
-> GeneralChoiceMap
to denote that (unlike a VectorChoiceMap) there are no restrictions on the key type.
It also seems reasonable to me to use DynamicChoiceMap
for now even if it isn't super well motivated, just so things read similarly to Gen.jl for now. We could then possibly do a pass reconsidering names like this across all the Gen variants after GenJAX and gen.clj are more feature complete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like these thoughts — I would recommend that we stay with the current name to match Gen.jl for now, and that if we make a shift we do it with Gen.jl too.
BUT there is one subtlety — there is both a DynamicChoiceMap
(which is mutable, yeehaw) AND a DynamicDSLChoiceMap
in Gen.jl. The latter is not mutable once it's returned by get_choices
, but is a wrapper around the trie
stored in the dynamic trace.
Dynamic is doing a lot of work in Gen :)
[v] | ||
(if (choicemap? v) | ||
(and (not (has-value? v)) | ||
(core/empty? v)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
am I right to understand - based on my quick googling - that this recursive call to core/empty?
calls count
on v? (or does it call the empty
function you defined on each of the types above?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think empty?
actually calls seq
and sees if it returns nil
. The empty
function WITHOUT the question mark returns an empty instance of the supplied type:
gen.choicemap-test> (empty {1 2 3 4})
{}
gen.choicemap-test> (empty [1 2 3])
[]
gen.choicemap-test> (empty '(1 2 3))
()
gen.choicemap-test> (empty (choicemap/choicemap [1 2 3]))
#gen/choicemap {}
"Tests for the [[gen.choicemap]] namespace and the various base implementations | ||
that live there." | ||
(:require [clojure.test :refer [deftest is testing]] | ||
[clojure.test.check.generators :as gen] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean gen
throughout the tests refers to clojure.test.check.generators
(not Gen the PPL)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup, correct, confusing here I guess but I think that's been their convention... that's not going to work in other namespaces though so I should change it!
test/gen/generators.cljc
Outdated
(gen/map k-gen v-gen)))) | ||
|
||
(defn gen-vector-choicemap | ||
"Returns a generator that produces [[gen.choicemap/Choice]] instances." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docstring typo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed, thank you!
(checking "not-found arity" 100 | ||
[m (generators/gen-dynamic-choicemap)] | ||
(is (= ::not-found | ||
(m ::not-present ::not-found)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any pointers to where I can learn what ::not-found and ::not-present mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
those are "namespaced keywords": https://blog.jeaye.com/2017/10/31/clojure-keywords/
So ::not-found
expands out to :gen.choicemap-test/not-found
. I use it here because the generative testing library is going to generate many random keywords and I want to make sure that my not-found "sentinel" doesn't clash with any of those.
(is (choicemap/empty? [])) | ||
(is (choicemap/empty? {}))) | ||
|
||
(testing "merge" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be good to add a test for merging hierarchical choicemaps?
E.g. {:k 1} with {:a {:b 1}}, and {:k 1} with {:k {:a 1}}. (Is the second case supposed to be an error?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added, and yes!
11113f9
to
9a75b78
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great comments, hopefully these responses help!
arr/IArray | ||
(to-array [_] [v]) | ||
(-from-array [_ xs idx] | ||
[1 (Choice. (nth xs idx))]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keep questions like this coming! also we should get a REPL up and pair soon.
This function wants a return value of a 2-element vector with
- how many items you took off of the input array
xs
- the new value you created with those elements
As described here https://github.com/InferenceQL/gen.clj/blob/main/src/gen/array.cljc#L10
So for Choice
, we take 1 thing off from xs
at start-idx
, and create the Choice
instance in the second slot.
(And yes you're right that it's creating the vector [1, Choice(,,,,)]
(-from-array [_ xs idx] | ||
[1 (Choice. (nth xs idx))]) | ||
|
||
#?@(:clj |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is surprisingly annoying in Clojure to extend the base interfaces!! these are flexed in the tests, and I tried to match the implementations in Gen.jl for things like getindex
. I'll comment below on what these do
|
||
#?@(:clj | ||
[Object | ||
(toString [this] (pr-str this)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basic equality and stringification
(equiv [this other] (-equiv this other)) | ||
|
||
IPrintWithWriter | ||
(-pr-writer [_ writer opts] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ClojureScript is more modular with its interfaces; this is how ClojureScript prints, and the interface below is more generic equality than the js equality above.
(and (instance? Choice o) | ||
(= v (.-v ^Choice o))))])) | ||
|
||
#?(:cljs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the next two methods use our print implementation with clojure's default printer and pretty printer.
[v] | ||
(if (choicemap? v) | ||
(and (not (has-value? v)) | ||
(core/empty? v)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think empty?
actually calls seq
and sees if it returns nil
. The empty
function WITHOUT the question mark returns an empty instance of the supplied type:
gen.choicemap-test> (empty {1 2 3 4})
{}
gen.choicemap-test> (empty [1 2 3])
[]
gen.choicemap-test> (empty '(1 2 3))
()
gen.choicemap-test> (empty (choicemap/choicemap [1 2 3]))
#gen/choicemap {}
"Tests for the [[gen.choicemap]] namespace and the various base implementations | ||
that live there." | ||
(:require [clojure.test :refer [deftest is testing]] | ||
[clojure.test.check.generators :as gen] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup, correct, confusing here I guess but I think that's been their convention... that's not going to work in other namespaces though so I should change it!
(checking "not-found arity" 100 | ||
[m (generators/gen-dynamic-choicemap)] | ||
(is (= ::not-found | ||
(m ::not-present ::not-found)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
those are "namespaced keywords": https://blog.jeaye.com/2017/10/31/clojure-keywords/
So ::not-found
expands out to :gen.choicemap-test/not-found
. I use it here because the generative testing library is going to generate many random keywords and I want to make sure that my not-found "sentinel" doesn't clash with any of those.
(is (choicemap/empty? [])) | ||
(is (choicemap/empty? {}))) | ||
|
||
(testing "merge" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added, and yes!
test/gen/generators.cljc
Outdated
(gen/map k-gen v-gen)))) | ||
|
||
(defn gen-vector-choicemap | ||
"Returns a generator that produces [[gen.choicemap/Choice]] instances." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed, thank you!
@georgematheos good Q... I am going to delete |
Thanks for the detailed responses -- looks great! |
This PR contains an implementation of the choicemap interface and data types found in Gen.jl. I stuck very closely to their behavior, sparsely implementing clojure.core methods where they were necessary or appropriate.
This PR follows #51 .
Also, a note for reviewers... the design here of using a "Choice" or "ValueChoiceMap" type follows GenJAX, as well as a design suggested by @georgematheos in these two PRs:
ValueChoiceMap
s Gen.jl#263We actually did the latter in #23, but
get-choices
wasn't actually returning anIChoiceMap
in all cases. This change fixes that, as George recognized.This change from Gen.jl is blessed by @alexlew, Vikash and @femtomc.