Fork me on GitHub
#beginners
<
2023-07-26
>
Jason Bullers02:07:10

Is there a nice idiom or core function for when you have a collection of functions and you want to apply them in turn to a value, transforming that value as it cascades through the functions? Silly example:

(def fs [inc inc dec inc])
  (loop [n 5 fs fs]
    (if (seq fs)
      (recur ((first fs) n) (rest fs))
      n))
  ;; => 7
I'm imagining something like the threading macros, but where the functions you thread through are determined dynamically.

Sam Ritchie02:07:19

here’s a clue:

(let [fs [inc inc dec inc]
      n  5]
  (reduce #(%2 %1) n fs))

Sam Ritchie02:07:41

(defn thrush [& args] 
  (reduce #(%2 %1) args))

(thrush 5 inc inc dec inc)

Sam Ritchie02:07:51

a couple of styles here :)

Jason Bullers03:07:45

Ah, interesting! Thanks for the link. He uses comp in reverse, too. I was thinking it's sort of a reduce, but I just couldn't get my head around the fact that I'm not "folding" the functions themselves, but making a pipeline. I guess that works though: the accumulator is the thing being transformed, which seems so obvious now that I see it.

❤️ 2
dpsutton03:07:33

(let [fs (repeat 5 inc), f (apply comp fs)] (f 0))

dpsutton03:07:09

user=> (doc comp)
-------------------------
clojure.core/comp
([] [f] [f g] [f g & fs])
  Takes a set of functions and returns a fn that is the composition
  of those fns.  The returned fn takes a variable number of args,
  applies the rightmost of fns to the args, the next
  fn (right-to-left) to the result, etc.

dpsutton03:07:20

that's what i would do over trying to understand what thrush means in a codebase. composition is straightforward. thrush is non-standard. certainly understandable but there's a built-in idiom

4
Jason Bullers17:07:31

You'd need to reverse fs before comping though, right? In this case, it doesn't matter, but in general the above would apply the transformations backwards. I like the reduction a bit better for that reason: less moving parts

Andrei Stan09:07:43

hi, is there any way to specify a return type hint for a function as clojure data structure? in this case a map :

(defn returns-a-map [a b]
{:arg1 a :arg2 b})

delaguardo09:07:07

(defn returns-a-map ^Type [a b]
  {:arg1 a :arg2 b})
You can specify returned type as a hint by adding it to the args vector. But in most cases you don't need to type hint functions that return clojure datastructures. Could you share in which context you think it is needed?

Andrei Stan09:07:26

I want to load a config file, which is file containing a clojure map

(defn load-config!
  "Returns the content of config file as a clojure data structure"
  [^String config]
  (let [config-path (.getAbsolutePath (io/file config))]
    (try
      (read-string (slurp config-path))
      (catch java.io.FileNotFoundException e
        (log/info "Missing config file" (.getMessage e)
                  "\nYou can use --config path_to_file to specify a path to config file")))))

delaguardo09:07:24

hm... type hints are useful only together with java interop. For example:

(defn foo ^java.util.Properties [] 
  (java.util.Properties.))

(.setProperty (foo) "qwe" "qwe")

delaguardo09:07:00

I don't think you need type hints at all in your example

Andrei Stan09:07:24

oh, ok, thank you:slightly_smiling_face:

delaguardo09:07:03

Are you using typehints as a "type system" marks for your code?

delaguardo09:07:00

I'm asking because there are few libraries that offers much better features to cover validation: clojure spec, malli, plumatic schema, etc.

Andrei Stan10:07:26

I am using type hints for documentation mostly and code readability, in this case another lib it is too much 🙂 thanks

Ben Sless10:07:32

The type hints are just metadata, not enforceable, so liable to rot like doc strings

☝️ 2
Andrei Stan16:07:53

hi, how to treat with clojure.lang.PersistentArrayMap ? i have a clojure map like this: {:connection #object[org.postgresql.jdbc.PgConnection 0xbf260d0 org.postgresql.jdbc.PgConnection@bf260d0]} and i want to print the value for :connection

dpsutton17:07:42

you are seeing it printed. A postgres connection is a complicated object and it just prints out it’s type and hashcode

Andrei Stan17:07:09

Oh, ok. How can i “expand” this object and extract data from it (eg. Host, port..etc)?

dpsutton17:07:29

objects have methods. if there’s a method that servers your purpose, call it

dpsutton17:07:10

the connection itself probably doesn’t have a notion of host and port. But i suspect where you got the connection does

lepistane19:07:50

Hello, I was looking into possibility could cause memory issue and found this https://ask.clojure.org/index.php/10532/memory-leak-using-the-default-method-of-a-multimethod if i understood right defmethod :default shouldn't be used because it can cause permanent leak? How to deal with this then ?

dpsutton19:07:35

> I don’t know if this is a leak so much as a design assumption that the number of dispatch values is bounded.

👍 2
dpsutton19:07:34

it’s a tradeoff. each time it sees a new value that dispatches to the default case it remembers that in order to be faster in the future (so it doesn’t have to recompute that it dispatches to the defaul). If you dispatch on reference identity objects, you’ll have this issue. Not with value based identity objects though.

;; reference based
(= (Object.) (Object.))
false
parse-test=> 
;; value based
(= [] [])
true

dpsutton19:07:11

i suspect it’s also not just to the default case though

dpsutton19:07:09

actually it does seem to just be the default case

dpsutton19:07:04

user=> (defmulti f :foo)
#'user/f
user=> (dotimes [_ 10000000] (f (java.util.HashMap. {:foo 1})))
nil
would be the repro fram that ticket and im’ not seeing an OOM

Alex Miller (Clojure team)19:07:05

the method cache is a map from dispatch value (output of the dispatch function) to a function

lepistane19:07:16

Oh i get it. Thanks a lot for the explanation. So this should be safe from that issue

(defmulti receive-feed :sportcode)

(defmethod receive-feed :default [msg]
  (timbre/info "Ignoring feed message - unsupported title"))

(defmethod receive-feed "CS" [msg]
  ;;whatever
  )
Because it's a value based and i don't expect infinite amount of variations in :sportcode i won't run into this issue.

Alex Miller (Clojure team)19:07:11

it is a cache for all successful dispatch values, so this can be an issue any time there are an arbitrary number of successful dispatch values. if there is a default method, then all values are successful dispatch values so that may increase the likelihood of this being an issue. in practice, most uses of defmultimethods use a small number of dispatch values.

👍 2
dpsutton20:07:00

is there an easy way to get the cardinality of the cache? I’d be interested to see if we’ve got some cache build up we haven’t noticed

Alex Miller (Clojure team)20:07:30

that methodCache field isn't public, but you could use reflection to go after it

👍 2
Alex Miller (Clojure team)20:07:55

or connect with a debugger