clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
Fabim 2021-05-13T10:59:50.337Z

Hi, I’m often want to add an optional values to a hash-map only if its not nil: (conj {:a 1} (when (:x m) {:x (:x m)})) Is there a more elegant way do do this?

2021-05-13T11:04:46.337100Z

(cond-> {:a 1}
  (:x m) (assoc :x (:x m)))

👍 1
Ed 2021-05-13T11:21:24.340400Z

bear in mind this case

(let [m {:x false}]
    (cond-> {:a 1}
      (:x m) (assoc :x (:x m))))
which I know is not covered by the original code, but is what you actually said, but you might want to consider this
(let [m {:x false}]
    (cond-> {:a 1}
      (some? (:x m)) (assoc :x (:x m))))
or
(let [m {:x false}]
    (merge {:a 1}
           (select-keys m [:x])))

Fabim 2021-05-13T11:23:20.340700Z

I read that merge and select-keys are expensive (to use for just one key)

Ed 2021-05-13T11:26:55.340900Z

depends what you mean by expensive ... sure, it's a few more function calls than a straight conj but it's not like calling a database or shelling out and it's not going to incur a huge gc pause or anything 😉

Ed 2021-05-13T11:27:42.341100Z

if you think it might be slow, measure it for your use case 😉

wotbrew 2021-05-13T11:48:10.341400Z

I just write a dissoc-nils helper for this case because I like using the literal syntax when building a larger map and the cond-> assoc certainly gets cumbersome if you have a lot of 'maybe' keys.

zendevil 2021-05-13T17:26:46.342400Z

How do I put the Authorization header in an clj-http post request?

fetch('<https://api.bannerbear.com/v2/images>', {
  method: 'GET',
  headers: {
    'Authorization' : `Bearer ${API_KEY}`
  }
})

zendevil 2021-05-13T17:27:14.342700Z

(post “https://api.bannerbear.com/v2/images” {???})

p-himik 2021-05-13T17:32:38.342900Z

Have you looked in the documentation?

zendevil 2021-05-13T17:34:25.343100Z

I didn’t find a way to add a header

p-himik 2021-05-13T17:35:31.343300Z

https://github.com/dakrone/clj-http#post

zendevil 2021-05-13T17:40:48.343700Z

thanks

zendevil 2021-05-13T17:41:14.344300Z

I’m sending this request:

(post "<https://api.bannerbear.com/v2/images>" {:headers {"Authorization" "Bearer DT56EIQ3irkCLvAAYTzGfgtt"}                            :form-params {:template "j14WwV5VkY4Da7XrBx"                                              :modifications                                                           [                                                            {                                      :name "text_container_0"                              :text "You can change this text"                                                    :color nil                                               :background nil                                                             }                                                            {                                              :name "circle_1"                                                    :color nil                                                             }                                                            {                                         :name "star_rating_2"                                                              :rating 70                                                          }                                                ]                                                            :webhook_url nil                                                           :transparent false                                                            :metadata nil                                          }})
But getting: “{\“message\“:\“Invalid parameter: modifications objects is empty or not an array\“}”

Kenneth Cheung 2021-05-13T17:46:16.344800Z

I'm working on performance optimizations to use unboxed arithmetic by using primitive type hints. I'm not getting the speed improvement I was hoping for. In inspecting the java code that is translated by Clojure, it seems the efficient version of the function isn't being called. Here's an example

(defn my-add
  [^double a ^double b]
  (+ 1.0 a b))
Using clj-java-decompiler.core/decompile
(decompile (defn my-add
             [^double a ^double b]
             (+ 1.0 a b)))
=&gt;
// Decompiling class: user$my_add
import clojure.lang.*;

public final class user$my_add extends AFunction implements DDO
{
    public static Object invokeStatic(final double a, final double y) {
        return Numbers.add(1.0 + a, y);
    }
    
    @Override
    public Object invoke(final Object o, final Object o2) {
        return invokeStatic(RT.doubleCast(o), RT.doubleCast(o2));
    }
    
    @Override
    public final Object invokePrim(final double a, final double n) {
        return invokeStatic(a, n);
    }
}
Three functions are created invokeStatic, invoke, and invokePrim. The efficient code being invokePrim and invokeStatic as these are properly typed. invoke on the other hand incurs some runtime penalty with the RT.longCast Now let's look at the decompile of using this function
(decompile (my-add 2.0 3.0))

// Decompiling class: cjd__init
import clojure.lang.*;
public class cjd__init
{
    public static final Var const__0;
    public static final Object const__1;
    public static final Object const__2;
    public static void load() {
        ((IFn)cjd__init.const__0.getRawRoot()).invoke(cjd__init.const__1, cjd__init.const__2); &lt;-- Notice here we are calling invoke not invokeStatic
    }
    public static void __init0() {
        const__0 = RT.var("user", "my-add");
        const__1 = 2.0;
        const__2 = 3.0;
    }
    static {
        __init0();
        Compiler.pushNSandLoader(RT.classForName("cjd__init").getClassLoader());
        try {
            load();
            Var.popThreadBindings();
        }
        finally {
            Var.popThreadBindings();
        }
    }
}
I'm noticing that in the load fn invoke is actually being called, and not invokeStatic or invokPrim . Is there a way to make sure I use invokStatic or invokePrim ? Thank you if you have gotten this far to my long post.

lukasz 2021-05-13T17:46:38.344900Z

Is the API you're working with use form params or JSON body? From the error message you're getting your problem is not auth, but the fact that you're not json-encoding the request body

p-himik 2021-05-13T17:48:28.345400Z

Your first version of my-add uses double, the decompiled version uses long. If you're using the latter, I imagine that's the reason, because 2.0 and 3.0 are not longs.

zendevil 2021-05-13T17:51:27.345900Z

This:

(post "<https://api.bannerbear.com/v2/images>" {:headers {"Authorization" "Bearer DT56EIQ3irkCLvAAYTzGfgtt"}
                                              :body (cheshire/generate-string
                                                     {:template "j14WwV5VkY4Da7XrBx"
                                                      :modifications
                                                      [
                                                       {
                                                        :name "text_container_0"
                                                        :text "You can change this text"
                                                        :color nil
                                                        :background nil
                                                        }
                                                       {
                                                        :name "circle_1"
                                                        :color nil
                                                        }
                                                       {
                                                        :name "star_rating_2"
                                                        :rating 70
                                                        }
                                                       ]
                                                      :webhook_url nil
                                                      :transparent false
                                                      :metadata nil
                                                      })})
gives:

Kenneth Cheung 2021-05-13T17:52:05.346200Z

yes sorry, fixed it. Pasted the wrong output from my repl

zendevil 2021-05-13T17:52:11.346400Z

“{\“message\“:\“Required parameter: template\“}”

alexmiller 2021-05-13T17:54:30.347700Z

I would look dubiously at decompile too. It is better to look directly at the generated bytecode

👍 1
jumar 2021-05-13T17:57:21.347900Z

Alex has an interesting blog post about these things here https://insideclojure.org/2014/12/15/warn-on-boxed/ 🙂

2021-05-13T17:58:19.348200Z

I would try looking at the decompiled output of (fn [] (my-add 2.0 3.0)) instead of the expression directly

jumar 2021-05-13T18:02:05.349100Z

Yeah, what hiredman mentioned is interesting:

(defn my-add-long
  [^long a ^long b]
  (+ 1 a b))

(decompile (fn [] (my-add-long 2 3)))
;;=&gt; 
// Decompiling class: clojure_experiments/performance/performance$fn__28976
package clojure_experiments.performance;

import clojure.lang.*;

public final class performance$fn__28976 extends AFunction
{
    public static final Var const__0;
    
    public static Object invokeStatic() {
        return ((LLO)performance$fn__28976.const__0.getRawRoot()).invokePrim(2L, 3L);
    }
    
    @Override
    public Object invoke() {
        return invokeStatic();
    }
    
    static {
        const__0 = RT.var("clojure-experiments.performance.performance", "my-add-long");
    }
}

Kenneth Cheung 2021-05-13T18:02:28.349700Z

@hiredman oh! Looks like this indeed calls invokeStatic

// Decompiling class: user$fn__7239
import clojure.lang.*;

public final class user$fn__7239 extends AFunction
{
    public static final Var const__0;
    
    public static Object invokeStatic() {
        return ((DDO)user$fn__7239.const__0.getRawRoot()).invokePrim(2.0, 3.0);
    }
    
    @Override
    public Object invoke() {
        return invokeStatic();
    }
    
    static {
        const__0 = RT.var("user", "my-add");
    }
}

Kenneth Cheung 2021-05-13T18:03:27.349800Z

interesting! Does this mean I should wrap all my calls in an anonymous function?

2021-05-13T18:05:12.350100Z

no

lukasz 2021-05-13T18:07:08.350300Z

I'm not familiar with the API you're using - the shape of the data is probably not right

2021-05-13T18:07:19.350500Z

it means function calls compiled inside a function are more representative of how function calls are compiled then top level function calls in namespaces

zendevil 2021-05-13T18:07:38.350700Z

https://developers.bannerbear.com/

zendevil 2021-05-13T18:08:14.351Z

The supposed body for this particular request:

{
  "template": "j14WwV5VkY4Da7XrBx",
  "modifications": [
    {
      "name": "text_container_0",
      "text": "You can change this text",
      "color": null,
      "background": null
    },
    {
      "name": "circle_1",
      "color": null
    },
    {
      "name": "star_rating_2",
      "rating": 70
    }
  ],
  "webhook_url": null,
  "transparent": false,
  "metadata": null
}

Kenneth Cheung 2021-05-13T18:09:46.351200Z

oh I see. Thank you for this clarification!

Kenneth Cheung 2021-05-13T18:24:44.352800Z

@alexmiller Looking at the byte code. Indeed it calls invoke.Prim . Thanks

(ns scratch.primitive)

(defn my-add
  [^double a ^double b]
  (+ 1.0 a b))

(defn test-fn []
  (my-add 2.0 3.0))

Byte Code
public final class scratch.primitive$test_fn extends clojure.lang.AFunction {
  public static final clojure.lang.Var const__0;
    descriptor: Lclojure/lang/Var;

  public scratch.primitive$test_fn();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #9                  // Method clojure/lang/AFunction."&lt;init&gt;":()V
       4: return
    LineNumberTable:
      line 7: 0

  public static java.lang.Object invokeStatic();
    descriptor: ()Ljava/lang/Object;
    Code:
       0: getstatic     #15                 // Field const__0:Lclojure/lang/Var;
       3: invokevirtual #20                 // Method clojure/lang/Var.getRawRoot:()Ljava/lang/Object;
       6: checkcast     #22                 // class clojure/lang/IFn$DDO
       9: ldc2_w        #23                 // double 2.0d
      12: ldc2_w        #25                 // double 3.0d
      15: invokeinterface #30,  5           // InterfaceMethod clojure/lang/IFn$DDO.invokePrim:(DD)Ljava/lang/Object;
      20: areturn
    LineNumberTable:
      line 7: 0
      line 8: 15

  public java.lang.Object invoke();
    descriptor: ()Ljava/lang/Object;
    Code:
       0: invokestatic  #33                 // Method invokeStatic:()Ljava/lang/Object;
       3: areturn
    LineNumberTable:
      line 7: 0

  public static {};
    descriptor: ()V
    Code:
       0: ldc           #36                 // String scratch.primitive
       2: ldc           #38                 // String my-add
       4: invokestatic  #44                 // Method clojure/lang/RT.var:(Ljava/lang/String;Ljava/lang/String;)Lclojure/lang/Var;
       7: checkcast     #17                 // class clojure/lang/Var
      10: putstatic     #15                 // Field const__0:Lclojure/lang/Var;
      13: return
    LineNumberTable:
      line 7: 0
}

seancorfield 2021-05-13T18:31:04.353Z

@ps It may require "Content-Type" "application/json" in the headers as well, in order to trigger JSON handling on their end.

zendevil 2021-05-13T18:32:49.353200Z

@seancorfield 🙇

seancorfield 2021-05-13T18:39:19.353400Z

(I’m just basing that on what’s in their docs — which I’d never seen before 🙂 )

lilactown 2021-05-13T21:57:00.354200Z

I remember Clojure usage stats being bandied about every now and then. anyone know where I can find those?

lilactown 2021-05-14T14:06:35.361100Z

great, thanks!

seancorfield 2021-05-13T22:49:13.354300Z

What do you mean by “usage stats”? Are you thinking of the State of Clojure survey/results?

seancorfield 2021-05-13T22:49:22.354500Z

If so, that’s on http://clojure.org under news.