Fork me on GitHub
#beginners
<
2022-10-22
>
walterl01:10:53

Is there a way to translate a map {:x 1, :y 2} into (.setX obj 1) and (.setY obj 2) calls on a given Java object?

walterl01:10:09

Nevermind. Found clojure.java.data/to-java https://github.com/clojure/java.data

hifumi12302:10:41

That library is really great and I can vouch for it! I will warn that if you try using from-java, then be careful with objects that have a lot of fields. You could get a stack overflow if you deeply convert java objects. To work around this, it's usually good to making a from-java method dispatching on the object in question, and you pick which fields you want to process

👍 1
seancorfield03:10:47

@U0479UCF48H Note that there are functions to convert both deep and shallow (`from-java` is deep) and there are also numerous options you can use to avoid conversion of unwanted fields in an object (for exactly this reason): https://cljdoc.org/d/org.clojure/java.data/1.0.95/api/clojure.java.data#from-java-shallow

seancorfield03:10:43

@UJY23QLS1 Happy to answer any Qs you might have about java.data since I maintain it now. DM or in channel -- either is fine.

❤️ 1
hifumi12305:10:39

@U04V70XH6 Yeah, I'm aware of from-java-shallow. Sometimes I find myself dealing with an object with tons of fields but I still want to deeply convert a subset of these fields, but not have to handwrite dozens of exclusions myself. In these cases I (defmethod from-java ...) and it works out pretty elegantly in my experience. In any case, this is a great library and thanks for bringing this up! 🙂

seancorfield05:10:47

If you have suggestions for making those use cases easier, feel free to post on http://ask.clojure.org and I'll take a look. It's one of those "quiet workhorse" style libraries 🙂

👍 1
hifumi12305:10:14

Honestly, the design is pretty sane as it is. I have had use cases for from-java-shallow, and specifying exclusions (rather than inclusions) is more often than not the "right thing" to do when converting from java objects. If I do have a feature request later on, I definitely will post on ask clojure though!

1
cheewah11:10:43

Say I have a fn

(defn f [arg1 arg2] … )
and a vector
[a1 a2 a3 …]
I want to chain f on the vector such that arg1 is the item in vector and arg2 is the previous output, like the following if fully expanded
(… (f a3 (f a2 (f a1 {}))))
Is there an idiomatic way for above

Martin Půda11:10:39

(reduce #(f %2 %1) {} [a1 a2 a3 ...])

👍 1
cheewah11:10:59

Thanks! Nice and elegant 😉

Jim Strieter11:10:54

I run a database query that might fail. When that happens, the function returns :query-failed. Before using results, I check if that name is a keyword, like this:

(let [results (query-db ...)] ;; query-db returns :search-failed if no results are found
  (if (keyword? results)
    (handle-failure ...)
    ;; This line and below I should be able to assume results is a list
    (if (empty? results)
      ...
    )))
I keep getting this error:
Can't create IFn from keyword
Why isn't (keyword? results) evaluating to true? This problem is driving me nuts

walterl15:10:16

Hmmm... :thinking_face: On what line is it failing?

JB16:10:18

I think I might have bumped into something similar a few minutes ago on my own project. Attempted an 'if' against a native Java object that plays with JNI resulted in a message '.. does not define or inherit an implementation of the resolved method 'abstract java.lang.Object applyTo(clojure.lang.ISeq)' of interface clojure.lang.IFn.' I think 'if' predicate doesn't operate as simply as it seems. Based upon your error, I feel like the keyword you are receiving does not have the standard Clojure method to also operate as a function. (Or you are getting another type in results but it prints a keyword as a result). I'd change it to either (if (= :search-failed results) ... ) or use a cond or case statement. If you can capture the result in a failed result in a (def) and the run type to confirm the returned value that will be tested. Hope that, if that doesn't help, it gets you moving in the right direction.

sarna12:10:09

hey, are protocolos something I should use often (like interfaces in OO langs) or something to use only when your codebase is large and you absolutely need some order to the code? for context: I've never used protocols before, I wanted to use them now but this post here says they bring problems for REPL-driven development :thinking_face: https://philomates.github.io/articles/2022-03-24-contextualizing-clojure-in-the-small-and-large/

walterl15:10:54

Overuse of protocols can definitely have negative consequences, even beyond the reloading issue. I try to only reach for them when absolutely necessary, and only after it becomes that normal functions won't cut it, and neither will multi-methods. Protocols work well for lowish level service wrappers, so that they can be mocked out during tests (as mentioned in the article you linked). potemkin.types/defprotocol+ pretty much solves the reloading issue.

sarna15:10:14

I see, thank you! hmm, is it the same with using defrecord instead of regular maps? :thinking_face: should I only use deftype/`defrecord` only when necessary?

walterl15:10:47

I tend to only use records to satisfy ("implement") protocols. For other cases maps are usually sufficient, and more flexible. In general you don't want to complect data with functionality, as Rich Hickey explains in https://www.infoq.com/presentations/Simple-Made-Easy

walterl15:10:13

I.e. resist the urge to OOP 😉

sarna15:10:04

yeah, I'm still struggling with that a bit. do you have any "constructor" functions that would create a map with fields that functions expect? for example in a namespace foo, would it make sense to include foo/new that'd return a map? otherwise I have to look at all foo/do-x, foo/do-y.. to see what fields my map should have so I can pass it to these

walterl15:10:31

If you need a map with specific fields, just create a map with those fields. 🙂 There's really no benefit to moving that to a separate function. Unless there's more logic that always accompanies the construction of such a map. The Clojure Way is to do the simplest thing, until changing it would make the code simpler. Applying that to the above: if you're unsure if you need a "constructor" function (or a protocol, or a type, or a ...), you probably don't. When it becomes clear from your code that you need it, do it. Your question reminds me of a https://clojureverse.org/t/organizing-clojure-code-a-real-problem/7567/26 I happened to read last night around organizing code. It would perhaps be worth reading.

sarna15:10:45

I'll give it a read :) I probably have to get used to organizing code in this way, for now it feels a bit chaotic and weird haha. but all new stuff is always a bit weird. thank you for the help!

walterl15:10:20

Yeah, that feeling is normal. It gets better, but I still have it sometimes. 🙂

😀 1
seancorfield17:10:33

This is good advice when you're thinking about data types and OO in Clojure: https://github.com/cemerick/clojure-type-selection-flowchart

👍 1
sarna18:10:11

thanks! "will the type participate in clojure protocols?" is a bit tricky, you can always add a protocol 😆

seancorfield18:10:20

Protocols are useful when you know you have at least two implementations of some functionality for something that is type-based. Here's some stats of our code at work as a sanity check on the use of protocols:

Clojure build/config 22 files 403 total loc,
    154 deps.edn files, 2894 loc.
Clojure source 518 files 105424 total loc,
    4503 fns, 1085 of which are private,
    634 vars, 41 macros, 102 atoms,
    86 agents, 22 protocols, 65 records,
    821 specs, 25 function specs.
Clojure tests 536 files 26419 total loc,
    5 specs, 1 function specs.

Clojure total 1054 files 131843 total loc

Polylith:
   17 bases 340 files 53967 total loc,
   107 components 535 files 43223 total loc,
   19 projects 0 files 0 loc,
  total 875 files 97190 total loc.
Migration 83.01% files 73.71% loc

seancorfield18:10:55

Most of our records are for Component which already has a Lifecycle protocol for start/`stop` functions.

seancorfield18:10:17

Most of our protocols are where we have wrappers for multiple externals systems and we want to be able to conform them to a common API, and most of those protocols are for a single function, e.g., validate-payment-method, and then we have a record for each specific external service, that has fields for whatever specific configuration the service needs.

sarna18:10:31

that's very useful, thanks!

walterl20:10:21

> "...most of those protocols are for a single function... This is very interesting. What is this approach called, and do you know where I can learn more about it?

walterl20:10:33

Come to think of it, what's the benefit over (defmulti _ [_] type)? Accompanying state?

seancorfield22:10:36

Protocols are good for dispatching on the type of a single argument. Multimethods are good for dispatching on data/values and/or multiple arguments.

seancorfield22:10:41

In Java, an interface with a single method is called a "functional interface" I believe but I don't know that Clojure protocols have a specific name for this -- but keeping protocols small is considered idiomatic I believe.

👍 1
craftybones04:10:39

I like using protocols to abstract components away. For instance, a database. This provides such tremendous flexibility to go from an in memory database to start with and then move onto something concrete when needed

craftybones04:10:25

I use integrant though, not component which does that anyway