Fork me on GitHub
#clojure
<
2022-12-15
>
UlisesMAC00:12:55

Hi everyone, I need the result of applying filter and remove to a coll. i.e.

(let [a (filter ,,, coll)
      b (remove ,,, coll)]
[a b]) ;; <- could be a vector or map
and I would like to get the result by just applying one function, I already checked group-by, but my problem is that it generates a map where the keys are true and false, it doesn't look fine. Do you know how to make this in a more idiomatic way? Thanks!

seancorfield00:12:43

(let [result (group-by pred coll)]
  [(get result true) (get result false)])
☝️:skin-tone-2: would that give you what you need @U02TF2JU3M4

UlisesMAC00:12:41

@U04V70XH6 I would like not to have the true and false keys :thinking_face: since (get result true) or (result true) don't look very good. I was wondering if there's a better way to achieve this

seancorfield00:12:38

You could wrap the above in a function and just not look at that code any more 🙂

3
👍 2
1
UlisesMAC00:12:44

Yep, also I was thinking in modifying the result of pred to return a keyword instead of true and false :thinking_face: anyway!

seancorfield00:12:06

There is a difference in semantics as far as laziness is concerned: filter and remove are both lazy but group-by is eager. You could also use a predicate function that returned :filtered or :removed instead of just a Boolean.

👍 1
seancorfield00:12:33

(group-by #(if (pred %) :filtered :removed) coll)

👍 1
seancorfield00:12:01

Then you'll get a map with those keys instead of true and false.

UlisesMAC00:12:36

Thanks again!

Martin Půda04:12:01

((juxt filter remove) odd? (range 10))
=> [(1 3 5 7 9) (0 2 4 6 8)]

1
juxt 1
🙌 1
Alex Shulgin08:12:31

> juxt The group-by approach has the benefit of calling the predicate once per element, however.

winsome01:12:16

Is there a way to extend a record to implement a new protocol at runtime?

skylize01:12:04

You can extend a Record during the type declaration.

;; from memory, might have bugs

(defrecord Foo [x y]
  extend Bar
  (bar-method [this z] (implementation this z)))

(let [foo (->Foo :foo :bar)]
  (bar-method foo :baz))

skylize01:12:40

In that ^ example Bar would be your protocol ...

(defprotocol Bar
  (bar-method [this])
... to which you extend Foo with an implementation of its methods.

emccue02:12:41

yeah you can use (extend

emccue02:12:52

technically speaking all the record stuff happens at runtime

emccue02:12:24

but you can't make it implement a new interface after declaration - you need to use the fallback protocol dispatch mechanism (which isn't that bad)

emccue02:12:42

(you can redeclare but...at that point idk what your concern is)

winsome02:12:38

So, if I have a Foo record that implements the Bar protocol, I can't extend the already-instantiated record to also implement the Bam protocol? I suppose I can work with that.

emccue02:12:12

just use extend /extend-type

emccue02:12:37

it will implement the protocol, just not extend the interface - minor diff depending on what you are doing

winsome02:12:48

That means I can use the method but I can't use it for polymorphic dispatch, right?

seancorfield04:12:39

If the "new protocol" is extensible via metadata, you can add metadata to any Clojure object at runtime and polymorphic dispatch will work.

Drew Verlee05:12:20

I'm trying to make macro that can destructure a map. Here is what i have so far:

(defmacro <-
    [m]
    `'('let [{:keys (destructure ~m)} ~m]
            1)

  (def args {:a :b})

  (<- args)
  ;; => ((quote clojure.core/let)
  ;;     [{:keys (centriq-web.jobs.import.fix.import-all-data-assets/destructure args)}
  ;;      args]
  ;;     1)

i can't tell if thats closer then this

  (defmacro <-
    [m]
    `[{:keys (destructure ~m)} ~m])

  (<- args)
  ;; => [{:keys [a]} {:a :b}]
I get lost on what the plan should be when i introduce the let binding. I imagine it helps first to imagine why it has to be a macro, in this case it's because i need the hashmap passed to be turned into a vector of symbols (thats what destructure fn does), which in turn is returned as part of a let expression. The interaction with the let binding inside the macro has me confused though. for example
(defmacro <-
    [m]
    `'(let [{:keys [~(destructure ~m)]} ~m]
        1))

  (macroexpand-1
   `(<- args))

Fails with 
   Attempting to call unbound fn: #'clojure.core/unquote
~(desctructure ... needs to happen because i need the hashmap to turn into a vector and i think the unbound unquote is the m in (desctructure m) but i can't leave it unquoted because i need to send it the map not a symbol. Maybe if i inline the function this will be easier?

Drew Verlee05:12:32

i just noticed how late it is. 🛏️

valerauko06:12:51

what is it you're trying to achieve?

Drew Verlee12:12:44

Sorry, let me clarify. Given (def x {:a 1}) The input would be: x The output would be (let [{:keys [a]} x] [a]) This macro would be a useful development tool and save me having to do more typing.

valerauko13:12:47

isn't that just vals?

chouser14:12:48

I'm not sure this is what you're asking for, but I can imagine it'd be useful to have a macro that takes a map and a body, like (<- x (prn a)) that expands to (let [{:keys [a]} x] (prn a)) . So that all the map keys are available as locals. But this would be quite tricky to write, because the keys of x wouldn't be known at macroexpand time.

Drew Verlee14:12:29

Why wouldn't they be known? If it look at my examples, getting the keys out wasn't the issue, It was putting it in the let block. @UAEH11THP i don't follow (Vals x) => 1 I want (<- x) => (let [{:keys [a]} x] [a]) It helps so i don't have to type all the keys to x if there are like 10 of them and i need to do something with them in the let block. It's a time saver when developing locally.

chouser15:12:15

To see the code that a macro produces, uses macroexpand-1, like this: (macroexpand-1 '(<- x)) That should show you the code that would then be compiled. If you use the macro directly without macroexpand, you'll see the result of evaluating the returned code -- this value usually shouldn't look like code anymore (that is, not a let block like (let [...] ...), but rather the result you hope to see at runtime.

valerauko16:12:14

(let [{:keys [a]} x] [a]) if the map x only has the key :a then this will be the same as vals though? if you want to put "something else" where the [a] is while having the binding a without knowing the keys, i'd rather just not do that. though i'm not even sure if the compiler would let you. i think it'd yell at you to use auto-gensym names. can you show an usecase for this thing you're trying to do? i can't imagine how it'd be useful

Drew Verlee16:12:48

Imagine the hashmap has like 5 or 6 arguments with really long names. Those names are not going to be autcompleted in my editor, because they don't exist in the static text. So right now i first eval the map so i don't have to type but can get tab completion. Here is an example of what i would start with

(def args {:really.long.namespace.in.a.big/project 1
             :another.really.long.asdflkjallkjas/lffff334343 2})
after the macro
(let [{:keys [really.long.namespace.in.a.big/project
                another.really.long.asdflkjallkjas/lffff334343]} args]
    [project lffff334343])
With some manual editing by me to get what i want
(let [{:keys [really.long.namespace.in.a.big/project
                another.really.long.asdflkjallkjas/lffff334343]} args]
    (+ (http-request project)
       lffff334343))
without the macro how many keystrokes would it take to get from the first step to the second? I want it to be 1. @UAEH11THP hopefully that helps.

valerauko01:12:38

I think this is more fit for an editor extension (a calva feature) than a macro Unless your args are known (static) at macro expansion time I don't think it's possible at all

Drew Verlee01:12:26

I think that's true, the keys are only known at run time in cases where the complier can't reach them.

Drew Verlee01:12:51

Yeah, the goal was going to be to hook it up to my editor. Though, i think, the fn would be just clojure outside the UI.

valerauko01:12:10

I don't know editor extensions but auto-completing \args to the current value of the args map shouldn't be hard in the REPL With a known map you can do something like

user=> (defmacro <- [args]
  `(let [{:keys ~(mapv (comp symbol name) (keys args))} ~args]
     ;;cursor here
     ""))
#'user/<-
user=> (macroexpand-1 `(<- {:a 1 :b 2}))
(clojure.core/let [{:keys [b a]} {:b 2, :a 1}] "")

👀 1
🎉 1
Drew Verlee01:12:20

I'll take a look, thanks a lot for the help.

valerauko01:12:44

(if you use namespaced keys use symbol instead of (comp symbol name))

Ajithkumar10:12:15

Hi @here , How to convert the below map literal with namespace from=>

#:person{:first "Han"
         :last "Solo"
         :ship #:ship{:name "Millennium Falcon"
                      :model "YT-1300f light freighter"}}
to=>
{:person/first "Han"
 :person/last "Solo"
 :person/ship {:ship/name "Millennium Falcon"
               :ship/model "YT-1300f light freighter"}}

cjohansen10:12:09

It’s a print formatting thing. Evaluate this.

(set! *print-namespace-maps* false)

thanks2 1
metal 1
mlimotte15:12:19

Aji, those data structures are the same. The difference is just in how they are displayed. Purely visual and due to your REPLs print settings.

clojure-spin 1
thanks2 1
chouser14:12:43

Does anyone know of a way to configure logback so that any clojure.lang.ExceptionInfo that gets logged will have its ex-data included in the output? In particular, I'd like it included as JSON in the JsonLayout.

kwladyka15:12:02

I don’t know, but I know how to do it with JUL

kwladyka15:12:34

I think you have to do something very similar, but with logback classes

kwladyka15:12:28

in shortcut it converts log into json, but you have also (assoc :ex-info (ex-data thrown)) there

kwladyka15:12:37

and ex-info? (= clojure.lang.ExceptionInfo (class thrown))

kwladyka15:12:05

so this is the way to custom your logs in 100% how you want

kwladyka15:12:16

without any tradeoffs

kwladyka15:12:34

the choice is your which solution you will choose 🙂

chouser15:12:36

yeah, I see. Thanks. I wonder what value we're getting out of logback -- configuring it from Clojure and XML seems trickier than what that example shows for JUL.

kwladyka15:12:54

long story, but I prefer JUL

kwladyka15:12:10

JUL changed since Java 9 if I remember

kwladyka15:12:30

and at least in my personal opinion is good

kwladyka15:12:56

in other words I would always choose JUL to do custom logging. Not something which is ready by default.

kwladyka15:12:09

But if you want something ready by default other options can be better

chouser15:12:23

I see. The JUL defaults aren't great, but it's better for customizing than logback?

kwladyka15:12:08

I guess this can be example: let’s say you want to log to a file, but you want to keep 1000 lines in a file and 10 files. It is not included in JUL by default as far as I know.

kwladyka15:12:14

But… who log to files in this days.

kwladyka15:12:43

I think it represents pros and cons clearly. This kind of things.

kwladyka15:12:35

But if you want to make custom JSON structure logging which is not ready by default anywhere it is a nightmare. At least it was extremely frustrating experience 2 years ago. Then I switched to JUL.

kwladyka15:12:41

I didn’t check it again from that time

chouser15:12:53

Sure. Ok, thanks for your help!

👍 1
msolli08:12:32

@U050BHA78 I researched this a few months back, and ended up with a small custom logger macro that extracts the exception data with ex-data and passes it as a net.logstash.logback.argument.StructuredArgument to the Logger methods (debug, info etc.). In the Logback config I use net.logstash.logback.encoder.LogstashEncoder to output log lines as JSON.

kwladyka12:12:36

@U06BEJGKD out of my curiosity how it looks today to make a custom JOSN logs? Not default one, but custom structure and values where for example if it is error it has to be very specific value in JSON.

kwladyka12:12:43

Can you paste a code?

kwladyka12:12:52

I am curious if something change in last years

kwladyka12:12:25

@UAEH11THP can mulog work as a forced default output logs for everything? I mean here Java libraries which use other logging frameworks. Can all this be redirected to the mulog?

kwladyka12:12:11

This is the biggest pain and at least in my opinion it is critical one to use or not use a tool for logging

valerauko12:12:39

should be doable with https://gitlab.com/nonseldiha/slf4j-mulog (never tried myself though)

👍 1
kwladyka12:12:31

although in first impression it doesn’t look to be maintained

kwladyka12:12:21

but it should be possible to maintain own library like this if someone really want to

kwladyka12:12:50

but I really like (μ/with-context {:order "abc123"} which is often needed for http session

kwladyka12:12:43

is it just binding under the hood?

kwladyka12:12:45

(defmacro thread-local-binding
  "Like the `binding` macro but for thread-local vars. (only 1 binding is supported)"
  {:style/indent 1}
  [binding & body]
  (when-not (and (vector? binding) (= 2 (count binding)))
    (throw (ex-info "the binding vector must be a clojure vector with 2 elements, symbol and value"
             {:bindings binding})))
  (let [[sym val] binding]
    `(let [^ClojureThreadLocal sym# ~sym
           val# ~val
           b# (deref sym#)]
       (.set sym# val#)
       (try ~@body (finally (.set sym# b#))))))
answer myself

kwladyka12:12:44

can’t be sure but if I understand this correctly it sets values for a thread, not wrap a code which will still not help for http sessions

kwladyka12:12:13

but maybe there is some logic for that in another place

valerauko12:12:54

i don't know what you're hoping for but with-context does propagate inside function calls

kwladyka12:12:33

so if the same fn in the same thread is called 10 times each of this call will have own values and not interfere each other?

kwladyka12:12:46

anyway this is interesting way to achieve adding values to logging by using (.set ^threadLocalValue …) instead of binding

msolli13:12:39

@U0WL6FA77 I’m specifically using https://github.com/logfellow/logstash-logback-encoder#event-specific-custom-fields to make Logback log any extra key/values as additional fields in the JSON output. Like I said, I have logging macro that extracts data from the exception. I’ll post a gist in a sec.

valerauko13:12:39

i don't know how could you call the same thing from the same thread multiple times in an interfering manner

kwladyka13:12:31

@U06BEJGKD

(with-values [… unique-request-id]
top level wrap
some async stuff
not async stuff
…)
I mean something like that

msolli13:12:17

https://gist.github.com/msolli/cbd9c7f7799f6696f8d3a5e3699e03f2 is my application’s logging implementation (“vilect” is my company’s name). It’s inspired by pedestal.log, but takes a more “structured logging” approach. And this is my logback.xml:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <encoder class="net.logstash.logback.encoder.LogstashEncoder">
    <fieldNames>
      <timestamp>timestamp</timestamp>
      <stackTrace>exception</stackTrace>
      <version>[ignore]</version>
      <levelValue>[ignore]</levelValue>
    </fieldNames>

    <excludeMdcKeyName>trace_flags</excludeMdcKeyName>
  </encoder>
</appender>

msolli13:12:34

Oh, I was referring to your earlier question about custom JSON logs.

kwladyka13:12:15

So as I understand you don’t have custom JSON logging structure

msolli13:12:05

I use the default one that Logstash dictates, with some minor modifications (see over). It is highly customizable, though.

👍 1
msolli13:12:22

Here’s the one I use for New Relic, for example:

<appender name="NEWRELIC" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <file>/var/log/web.newrelic.log</file>
  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>/var/log/newrelic/web.%d.log</fileNamePattern>
  </rollingPolicy>
  <encoder class="net.logstash.logback.encoder.LogstashEncoder">
    <timestampPattern>[UNIX_TIMESTAMP_AS_NUMBER]</timestampPattern>
    <fieldNames>
      <timestamp>timestamp</timestamp>
      <logger>logger.name</logger>
      <level>log.level</level>
      <thread>thread.name</thread>
      <stackTrace>error.stack</stackTrace>
      <version>[ignore]</version>
      <levelValue>[ignore]</levelValue>
    </fieldNames>

    <mdcKeyFieldName>trace_id=trace.id</mdcKeyFieldName>
    <mdcKeyFieldName>span_id=span.id</mdcKeyFieldName>
    <excludeMdcKeyName>trace_flags</excludeMdcKeyName>
    <excludeMdcKeyName>AWS-XRAY-TRACE-ID</excludeMdcKeyName>

    <customFields>{"service.name":"vilect-web"}</customFields>
  </encoder>
</appender>

kwladyka13:12:13

I thought you maybe use some “real” code to generate values and structure.

kwladyka13:12:24

I mean Clojure / Java

msolli13:12:34

The “real” code is in that gist. With those logging macros I get structured JSON logs. I can include any data I want when logging:

(log/debug ::something {:foo foo, :bar "some hardcoded value", :more-data something-data})
Or, to come back to OP’s question, if you want all the ExceptionInfo data included in the log output, I just:
(catch Exception e
  (log/error ::some-error {:additional "data"} e)
  nil #_"or whatever")

👍 1
Eugen22:12:53

this was asked a while ago. Has anything changed with regards to Java records interop - java.lang.Record ? Related question: should we try to update Clojure Java interop page with information regarding new JVM features? Mention new language features and how to implement them if possible. https://clojure.org/reference/java_interop > https://clojurians.slack.com/archives/C03S1KBA2/p1584980075120800

Alex Miller (Clojure team)23:12:40

Nothing has changed. I don’t think that’s the right place for this, but a guide maybe

Eugen23:12:18

kind of makes sense for a guide

Eugen23:12:39

tools like visualvm have more support for java classes. it would be great to have that. looking at clojure memory allocations is just array, string, char, map etc. which does not provide a lot of information .

Eugen23:12:00

records might provide a bit more

devn03:12:40

As an aside: what could we do as a community to make profilers better at diagnosing issues in our clojure programs? Who needs to get paid?

Ben Sless04:12:03

@U06DQC6MA what are you lacking in the current ecosystem? What sort of issues do you want to find?

Rupert (All Street)09:12:59

I find that I am able to use profilers like VisualVM to fix memory and performance issues to the same extent in Clojure as Java.

Eugen20:12:46

I will come back to this once I re-do the memory debugging steps in our app. It's been a while since I ran through the steps.