Fork me on GitHub
#beginners
<
2020-10-19
>
Noah Bogart13:10:18

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

Noah Bogart13:10:12

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"

Noah Bogart13:10:34

cool, i'll check that out! thank you

mathpunk16:10:50

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

schmee17:10:01

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

mathpunk16:10:38

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

noisesmith18:10:16

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

thanks2 3
Kenneth Cheung17:10:05

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

noisesmith18:10:54

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

Kenneth Cheung20:10:35

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.

Frederik18:10:50

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?

noisesmith18:10:55

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

Frederik19:10:15

more than good enough for me šŸ™‚

dpsutton18:10:09

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

noisesmith18:10:10

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

noisesmith18:10:47

@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

dpsutton19:10:11

ah neat. cool

noisesmith19:10:24

(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

noisesmith19:10:32

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

noisesmith19:10:11

(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

Frederik19:10:41

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

Frederik19:10:23

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?

noisesmith19:10:10

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)

noisesmith19:10:09

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

noisesmith19:10:22

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

hiredman19:10:11

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

šŸ’Æ 3
Frederik19:10:48

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

Frederik19:10:59

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

hiredman19:10:32

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

Frederik19:10:26

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

noisesmith19:10:01

why not a protocol, implemented by each type?

noisesmith19:10:13

if you in fact need types

Frederik20:10:36

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!

andy.fingerhut20:10:23

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.

andy.fingerhut20:10:30

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

andy.fingerhut20:10:38

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.

andy.fingerhut20:10:44

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.

andy.fingerhut20:10:17

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.

Frederik21:10:41

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 šŸ™‚

Frederik21:10:28

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

Frederik19:10:53

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

mathpunk19:10:29

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?

mathpunk19:10:57

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

mathpunk19:10:31

maybe I just should string/replace parts :thinking_face:

seancorfield19:10:58

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

mathpunk19:10:52

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

mathpunk19:10:03

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

mathpunk19:10:10

the string i mean

seancorfield19:10:54

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

mathpunk19:10:30

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

seancorfield19:10:00

I love tap> šŸ™‚

Michael Stokley19:10:39

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"}) ?

Michael Stokley19:10:20

or are keywords and strings idiomatically interchangeable, here?

Alex Miller (Clojure team)19:10:51

both are fine

šŸ™ 3
Alex Miller (Clojure team)20:10:06

keywords are slightly more flexible in being invokable as a function

dpsutton20:10:25

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

šŸ™ 3