I don't think you can do a lot of typing with spec no? Cause the predicates rarely map to a logical type?
It would have been nice if maybe spec had done something like: type + pred
So like the broader type + a refinement pred
And if I understand Typescript properly, a hord of devs jumped on it and retrofitted types for a ton of existing JavaScript. I mean this could happen for core.type as well but did not
Also personally, not sure how useful types would be without having abstract data types as the standard compound data-structure.
All maps would still just be of type map. So the issue of like what keys are in this? Am I using a key that doesn't exist? Etc. Would still be there
I think type script has row type polymorphism that can handle this. Not sure what it’s like in practice though
I think row type just means that any superset is automatically valid, even if not explicitly defined as is-a relationship.
But it would still require a definition of the fields on the type no?
Like I think you can do row polymorpism with Clojure records.
Yes that’s my understanding. Don’t know if core typed has this kind of analysis but would love it
Ya I think that's correct. Basically it just let's you say this function works on any type which have at minimum these keys of some given types. But it still requires the type checker to know what keys of what types the object has. So for it to work with maps, it would need that somehow it could infer for any given map at any point in the program what are the keys it would now contain and what types those keys would be of. Which seems like really hard to do. And that ignores that it's possible to have a dynamic key too, like where the key name is coming at run-time from external input
But records would work well with it. Cause records already force you to define the mandatory keys, you'd only need to also define their types and you could then do row polymorpism type checking, if your variables and function arguments specific row type constraints.
I just wrote this to cycle through a list of values in a stateful way, would be curious if others have better ideas on how to implement this. I think there might be a way with keeping a reference to a lazy seq but I don’t know if that’s a good idea and also I’m not sure how that would be done
(defn stateful-cycle [xs]
(let [state (atom (shuffle xs))]
(fn []
(let [r (first @state)]
(reset! state (conj (vec (rest @state)) r))
r))))
1👏slightly more compact version
(defn stateful-cycle3
[xs]
(let [state (atom (cycle (shuffle xs)))]
#(first (swap! state rest))))
cycle
is built into Clojure core library, and returns infinite (lazy) sequence that cycles through its input collection values.
If you want a stateful function that returns the elements in a cyclic order on multiple subsequent calls, you could put the return value of cycle
into an atom, and call first
and rest
/ next
on that sequence on each call.
There would be no difference in behavior from the implementation you show, but an implementation using cycle
would probably be more efficient in CPU cycles than yours, where you are building a vector of all elements of the cycle as a vector using vec
on every call.
The implementation you show is not safe to call from multiple threads, since it reads the current atom value twice, and modifies it once, with no synchronization. If you needed something that could be used from multiple threads correctly, you could make a pure function that takes the current value of the cycle, and returns the next value of the cycle, and call that function on the atom via swap!
Here is an implementation that should be safe to call from multiple threads, and uses cycle
for better efficiency (should be O(1) time per call, rather than O(n) where n is the number of elements in xs
):
(defn stateful-cycle2 [xs]
(let [state (atom (cycle xs))]
(fn []
(let [next-cycle (swap! state next)]
(first next-cycle)))))
I did not include the call to shuffle
like yours does. In terms of API, it seems better to me to allow the caller to make the decision to shuffle the sequence, or not, e.g. by calling (stateful-cycle2 (shuffle xs))
if they want randomization of order, or call (stateful-cycle2 xs)
if they do not.
"better" in the sense that the function is useful in more situations, because it does not force the shuffle
behavior.
Wow Andy, that’s great feedback all around! Good point about shuffle
as well
thread safety wasn’t really a concern since it’s JS but I like how your usage of next
just simplifies the whole thing
Another jvm-specific formulation that may be interesting:
(let [state-iter (.iterator ^Iterable (cycle xs))]
#(locking state-iter
(.next state-iter)))
Thank you @slipset:
(! 1012)-> clj -Sdeps '{:deps {org.clojure/data.json {:mvn/version "2.0.2"}}}' -A:1.11
Clojure 1.11.0-alpha1
user=> (require '[clojure.data.json :as json])
nil
user=> (json/write-str {:a 1 :b 2})
"{\"a\":1,\"b\":2}"
user=> (json/write-str {:a 1 :b 2} :value-fn (fn [_ v] (inc v)))
"{\"a\":2,\"b\":3}"
user=> (json/write-str {:a 1 :b 2} {:value-fn (fn [_ v] (inc v))}) ; Clojure 1.11 calling style
"{\"a\":2,\"b\":3}"
user=>
Now I can clean up a bit more code at work! 🙂
You’re welcome!