beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
NoahTheDuke 2020-10-19T13:09:18.170Z

hey friends, is there a way to rewrite clojure code in a file as if you're writing a macro, but separate from executing that code? I have a couple files with hundreds of map literals I need to update, and stepping through them one by one will be a lot of work vs writing a transformer once

NoahTheDuke 2020-10-19T13:10:12.170700Z

I know I could use string parsing, but that's much more error prone vs being able to say "is this a map? do x. is this a function? do y"

Toby Clemson 2020-10-19T13:10:42.171Z

rewrite-clj might help: https://github.com/xsc/rewrite-clj

NoahTheDuke 2020-10-19T13:20:34.171400Z

cool, i'll check that out! thank you

mathpunk 2020-10-19T16:41:50.173500Z

I think I'm looking for idiomatic application of apply.

(def out-of-scope [:color :size :shape]) ;; some keys i don't care about

      (defn summarize-data [body]
        (dissoc (json/parse-string body true) out-of-scope) ;; what I want to work

      (defn summarize-data [body]
        (dissoc (json/parse-string body true) :color :size :shape) ;; what actually works

mathpunk 2020-10-19T16:42:38.174200Z

(apply dissoc (into [] (json/parse-string body true) out-of-scope)) or something like that?

Kenneth Cheung 2020-10-19T17:11:05.174600Z

Hi all, does anyone know of a good parser to parse .data file extensions?

schmee 2020-10-19T17:38:01.175300Z

(apply dissoc (json/parse-string body true) out-of-scope) will do the trick!

2020-10-19T18:54:16.179500Z

you never need to create an extra collection for apply:

user=> (apply + 1 2 [3 4 5 6])
21
so (apply dissoc (json/parse-string ...) out-of-scope) suffices

1
Frederik 2020-10-19T18:55:50.180500Z

Hi! I'm looking into memoizing a function, but have two questions I could use your advice on: • The function I want to memoize takes instances of a deftype class (instances? what's the right name here?) as a parameter. If the field of those instances are the same, the result of the memoized function is the same too, but the memoize function seems to (as expected) to cache based on the identity of the instance. Is there an elegant way around it? I could take the field out of its instance, pass it into the function and then reinstantiate the deftype, but that doesn't seem very elegant or efficient. • Does the core memoization out of the box work in a parallel setting?

2020-10-19T18:55:54.180600Z

this extension has been used by multiple applications, which one do you need to handle?

2020-10-19T18:56:55.181100Z

memoize "works" in parallel situations, but makes no promise that the function is only called once

dpsutton 2020-10-19T18:58:09.182500Z

does clojure.core/memoize use weak or strong references? Wondering if cacheing instances as opposed to values might be a bad strategy

2020-10-19T18:58:10.182700Z

memoize uses =, it will use a cached value if the arg list is =

2020-10-19T18:59:47.183900Z

@dpsutton instance check is just the default implementation of = for deftype, you could override the hash and = functions for the type for a different behavior, or easier just use defrecord which uses value rather than instance equality

dpsutton 2020-10-19T19:00:11.184100Z

ah neat. cool

2020-10-19T19:00:24.184400Z

(ins)user=> (deftype Foo [a b])
user.Foo
(ins)user=> (= (->Foo 1 2) (->Foo 1 2))
false
(ins)user=> (defrecord Bar [a b])
user.Bar
(ins)user=> (= (->Bar 1 2) (->Bar 1 2))
true

2020-10-19T19:02:32.186100Z

you can implement the hashCode and equals methods on Object to change deftype's equality check

2020-10-19T19:05:11.188200Z

(cmd)user=> (deftype Baz [a b] Object (equals [this other] (and (= a (.a other)) (= b (.b other)))) (hashCode [this] (hash {:a a :b b})))
user.Baz
(ins)user=> (= (->Baz 1 2) (->Baz 1 2))
true

Frederik 2020-10-19T19:05:23.188500Z

I feel like there was a reason I chose deftype over defrecord, but now I'm not so sure... Gets a tiny bit more complicated though, my deftype has two parameters, one matters, the other one is (within one run of the recursive function that I'm memoizing) constant and big, so I'd like to cache based on only the first one. In that case, overwriting hashCode and equals seems the best approach?

Frederik 2020-10-19T19:05:41.188800Z

was just going to ask some pointers on how to approach this, awesome, thanks!

2020-10-19T19:06:10.189500Z

you should be able to adapt my example above (I use clojure.core/hash and an inline hash-map to leverage the logic clojure already has)

Frederik 2020-10-19T19:06:15.189600Z

more than good enough for me 🙂

2020-10-19T19:07:09.190500Z

beware that weird hashCode / equals behavior can combine to make spooky bugs when using your type as a key in a hash-map or member of a set

2020-10-19T19:07:22.190800Z

(or as an argument to a cached function...)

2020-10-19T19:08:11.192500Z

The best is just use a map, and cache only on the but you want to cache on

💯 1
Frederik 2020-10-19T19:08:59.193600Z

I'll keep it in mind, but should be fine as it's not used in too many places. The opposite did trip my up before when I switched from basic vectors to deftype, not realising every new deftype (even if they had the same values) would create a new entry in a hashmap

Frederik 2020-10-19T19:09:48.194Z

you mean write my own wrapper memoization function that uses a map and takes the value I ant to cache on?

2020-10-19T19:10:13.194400Z

Yes

2020-10-19T19:11:32.195700Z

If you are not sure why you chose deftype over defrecord, think odds are you don't need to be using either

Frederik 2020-10-19T19:14:26.198400Z

It looked the cleanest way to obtain multiple dispatch, as every time I introduce a new type I need a handful of functions implemented. I guess the alternative would be to use defmulti, but grouping them per function rather than per type seemed quite messy

Frederik 2020-10-19T19:14:53.199300Z

maybe I'm trying to impose object oriented coding on a language that should be used differently

mathpunk 2020-10-19T19:15:29.199800Z

I'm trying to speed up a workflow where I'm required (currently) to paste some JSON into a field. However,

(require '[cheshire.core :as json])
(json/generate-string {:name "This application was added via Postman"})
;; => "{\"name\":\"This application was added via Postman\"}"
Can I get rid of the escape coding expediently?

mathpunk 2020-10-19T19:16:57.200100Z

Note: I'm working at a repl that's sending tap data to reveal

mathpunk 2020-10-19T19:17:31.200600Z

maybe I just should string/replace parts :thinking_face:

seancorfield 2020-10-19T19:17:58.201100Z

@mathpunk (println (json/generate-string {:name "This application was added via Postman"})) will print it in copy'n'paste-friendly text

mathpunk 2020-10-19T19:18:52.201200Z

Oh it IS there -- I thought that I would get nil

mathpunk 2020-10-19T19:19:03.201400Z

and, I do, but it is present in the repl window

mathpunk 2020-10-19T19:19:10.201600Z

the string i mean

seancorfield 2020-10-19T19:19:54.201800Z

Right. The quoting is coming from using pr-str / prn in the REPL (and in Reveal). println produces different output (but also returns nil).

mathpunk 2020-10-19T19:21:30.202Z

btw, thanks for that (tap> ...) demo -- I think I'm using it in a pretty ham-handed way, but even still it's a nice experience over what i was doing before

seancorfield 2020-10-19T19:22:00.202200Z

I love tap> 🙂

2020-10-19T19:30:01.202400Z

why not a protocol, implemented by each type?

2020-10-19T19:30:13.202600Z

if you in fact need types

2020-10-19T19:58:39.204Z

what's the rationale for using keywords, and not strings, to model an enumeration? for example (def states #{:active :inactive :pending}) instead of (def states #{"active" "inactive" "pending"}) ?

2020-10-19T19:59:20.204800Z

or are keywords and strings idiomatically interchangeable, here?

alexmiller 2020-10-19T19:59:51.205500Z

both are fine

🙏 1
alexmiller 2020-10-19T20:00:06.206200Z

keywords are slightly more flexible in being invokable as a function

dpsutton 2020-10-19T20:00:25.206900Z

keywords implement IFn and look themselves up in collections when invoked. they also allow for namespaces. they can often cause annoyances when serialized into a db and require converting from string -> keyword and such

🙏 1
2020-10-19T20:01:32.207100Z

thanks!

Kenneth Cheung 2020-10-19T20:15:35.207500Z

Ah, thanks for the reply, but this seems to be a custom layout created by another engineer on the team. I'll need to write my own parser.

Frederik 2020-10-19T20:21:36.208100Z

That's what I did, I created a protocol:

(defprotocol GameState
  (finished-game? [this] "Tests if game is finished")
  (reward [this] "Gives reward for state")
  (get-available-actions [this] "Gives all available actions from this state")
  (apply-action [this action] "creates new state through action"))
And then implemented it with a deftype, for example:
(deftype TicTacToeState [rewards state-vector]
  GameState
   ....
The idea being that different games would have different implementations. Did this months ago, so forgot the exact reasons why, but hope that makes sense? Thanks for all the help btw!

2020-10-19T20:37:23.208600Z

defrecord is good for map-like immutable values that you want a protocol implementation for, with all of the fields 'exposed' as part of the value.

2020-10-19T20:38:30.208800Z

For deftype, you get to pick how you want clojure.core/= and clojure.core/hash to behave, so it becomes easy to create confusing situations where your definition of = doesn't make much sense for the type, and/or your implementation of hash isn't consistent with your definition of =.

2020-10-19T20:39:38.209Z

Since you mentioned wanting memoization to work on one field of a deftype object, but ignore another field, consider that memoization is not going to work for you, if the function you are memoizing ever uses the value of the field to determine the return value, but your = implementation ignores it.

2020-10-19T20:40:44.209200Z

A simple-minded similar example would be: imagine trying to create a memoized version of the function meta on Clojure collections. = between two Clojure collections ignores any metadata on the two collections, so would use the same memoization cache entry for two collections with different metadata. = says true, but the function meta explicitly uses the thing that = ignores in determining its return value.

2020-10-19T20:41:17.209400Z

The function you are memoizing may also be correctly ignoring the field that you want = to ignore, and if so, that is possible to make work.

Frederik 2020-10-19T21:26:53.209600Z

🙏

Frederik 2020-10-19T21:27:41.209800Z

All of that was very helpful, I feel like I should go back to clojure for the brave and true to get the familiarize myself with the differences between protocol, record and type 🙂

Frederik 2020-10-19T21:28:28.210Z

In the end I took the final solution from this blog: https://kotka.de/blog/2010/03/memoize_done_right.html And repurposed the "naive implementation" a bit for my use case, seems to work