Fork me on GitHub
#beginners
<
2021-10-26
>
Ty00:10:44

I constantly find myself writing expressions and eval'ing them just to check/test something and then delete the expressions. I quite enjoy it, but I just want to make sure I'm not performing some type of anti-pattern. Maybe this type of thing is better suited to tests?

dpsutton00:10:35

people say to never type in the repl but i don't get that. sounds perfect for actually typing in a repl. But making the comment blocks and sending things to the repl works just as fine. You aren't doing anything wrong and its how i often work

1
Ty01:10:44

Nice. I'm typically writing things directly in my .cljs files and then just running my editor command to eval a form

hiredman01:10:36

@seancorfield had some videos on working with the repl that may be interesting as well

fabrao02:10:57

hello, how do I resolve this conflict?

:import  
[ File FileInputStream InputStreamReader]
[com.google.api.services.drive.model File]
  for File?

hiredman03:10:24

The only thing import does is make it so you can refer to a class by its short name, so you can just not import File and refer to it by java.io.File

Nom Nom Mousse06:10:12

How do I sort this set by the map in the vector?

(def jobfiles #{[[:samtools-index {:genome "hg38", :sample "C"}] "bam/sorted.bam.bai"]
  [[:samtools-sort {:genome "hg38", :sample "C"}] "bam/sorted.bam"]
  [[:samtools-index {:genome "hg38", :sample "A"}] "bam/sorted.bam.bai"]
  [[:samtools-sort {:genome "hg38", :sample "B"}] "bam/sorted.bam"]
  [[:samtools-sort {:genome "hg38", :sample "A"}] "bam/sorted.bam"]
  [[:samtools-index {:genome "hg38", :sample "B"}] "bam/sorted.bam.bai"]})
I've tried many things like:
(map (comp second first) jobfiles)
 ({:genome "hg38", :sample "C"}
 {:genome "hg38", :sample "C"}
 {:genome "hg38", :sample "A"}
 {:genome "hg38", :sample "B"}
 {:genome "hg38", :sample "A"}
 {:genome "hg38", :sample "B"})
(sort-by (comp second first) jobfiles)
;; Execution error (ClassCastException) at java.util.TimSort/countRunAndMakeAscending (TimSort.java:355).
;; class clojure.lang.APersistentMap$ValSeq cannot be cast to class java.lang.Comparable (clojure.lang.APersistentMap$ValSeq is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')

pithyless06:10:18

Maps are not sortable; you could convert it to e.g. a sequence to sort it, but that would be potentially ambiguous. What is your expected behavior?

pithyless06:10:36

If you want to sort by specific keys, maybe something like this:

(sort-by
 (fn [[_tag m] _file]
   ((juxt :genome :sample) m))
 jobfiles)

Nom Nom Mousse06:10:46

I do not want to sort it by specific keys, but by the vals of the map 🙂 Thanks btw

pithyless06:10:38

right, but values in what order? :)

Nom Nom Mousse06:10:14

As long as the order is the same for every map it does not really matter.

pithyless06:10:41

remember that maps can theoretically have different set of keys and it's domain specific whether that is something that concerns you

Nom Nom Mousse06:10:07

In this case the fact that the keys might be different does not matter

pithyless06:10:28

so, just for fun here are some alternative sorting schemes: :)

(vals m) ;; ambiguous

(sort (vals m))

(mapv #(get m %) (sort (keys m)))

🙏 1
Mno08:10:17

sorted-map also exists.. but it's not something I'd make a habit of using

👍 1
didibus02:10:15

Just like this:

(sort-by #(-> % first second ((juxt :genome :sample))) jobfiles)

didibus02:10:54

Or if you want to be agnostic of the particular keys in the map you can do:

(sort-by #(let [m (-> % first second)] ((apply juxt (keys m)) m)) jobfiles)

didibus02:10:49

You can also choose to sort by the alphabetical sort of the keys in the map like so:

(sort-by #(let [m (-> % first second)] ((apply juxt (sort (keys m))) m)) jobfiles)

roelof09:10:26

What is the best way to call get-forecast here it schould be done after the handle-response-coordinates

pithyless17:10:51

@U0EGWJE3E you can actually have handle-response-coordinates call get-forecast! after it updates app-state. That way you can coordinate their order

pithyless17:10:16

aside: instead of

(swap! app-state
       update-in [:latitude] (constantly lat))
you can just use:
(swap! app-state
       assoc-in [:latitude] lat)

pithyless17:10:33

and it's best to not call swap! more than once, if not necessary; so I would revise your 2 swaps to a single call:

(swap! app-state
       (fn [state]
         (-> state
             (assoc-in [:latitude] lat)
             (assoc-in [:longtitude] lon))))

roelof17:10:32

@U05476190 oke but i think I have to put the code for making the coordinates under the code to find the weather data or im I wrong ?

roelof17:10:01

and I use two times swap because the orginal code did that too

roelof17:10:05

but I can change it

pithyless17:10:40

(defn get-forecast! ..)

(defn get-coordinates ..) ;; needs to be after, because it calls get-forecast!

(.. some code that calls get-coordinates ..)

pithyless17:10:51

^ do you mean order of defns in the file?

roelof18:10:10

chips. now I lost the code for displaying things

roelof18:10:17

I hope there is a clojurescript and hopefully someone who can help me

roelof18:10:28

I do not see what I done wrong

roelof18:10:18

I see this error in the console

goog.require could not find: learn_cljs.weather.core

roelof18:10:06

chips and I do not see any numbers back

roelof18:10:30

First look if the coordinates are put well in app-state

roelof18:10:45

and find out how to check

pithyless18:10:14

You can (pprint @app-state) in the handlers

roelof18:10:10

I did that

roelof18:10:17

but no luck

roelof18:10:54

I see this now at the dev console

Object { meta: null, cnt: 3, arr: (6) […], __hash: null, "cljs$lang$protocol_mask$partition0$": 16647951, "cljs$lang$protocol_mask$partition1$": 139268 }

__hash: null

arr: Array(6) [ {…}, 0, {…}, … ]

roelof18:10:04

and that makes no sense to me

pithyless18:10:35

looks like you're missing pretty-printing in Dev tools; are you using Chrome? https://github.com/binaryage/cljs-devtools

roelof18:10:53

no, im using FF

roelof18:10:01

but I can use chrome

pithyless18:10:17

practicalli has lots of good docs (if you haven't seen the site before)

roelof18:10:07

I know that site

roelof18:10:46

Could the trick work which we use that printing must be in the console ?

roelof18:10:28

chips nope

roelof18:10:06

maybe use the trick from yesterday to hard-code a city instead of user-input

roelof18:10:31

chips. frustating. I do something wrong here

(def raw-data (atom nil)) 

(defn get-coordinates []
  (let [zipcode (:zipcode "hengelo")]
    (ajax/GET ""
     {:params {"q" zipcode
                "units"  "metric"
                "appid" api-key}
      :handler (fn [resp] (reset! raw-data resp))})))

(comment 
  get-coordinates
  @raw-data
  )

roelof18:10:57

@raw-data is nil

roelof18:10:48

the url was wrong but also if I change things to

(defn get-coordinates []
  (let [zipcode (:zipcode "hengelo")]
    (ajax/GET ""
     {:params {"q" zipcode
               "appid" api-key}
      :handler (fn [resp] (reset! raw-data resp))})))
according to this page : https://openweathermap.org/api/geocoding-api

roelof18:10:30

@U05476190 do you see something wierd ?

roelof18:10:48

(def raw-data (atom nil)) 

(defn get-coordinates []
  (let [zipcode (:zipcode "hengelo")]
    (ajax/GET ""
     {:params {"q" zipcode
               "appid" api-key}
      :handler (fn [resp] (reset! raw-data resp))})))

(comment 
  get-coordinates
  @raw-data
  )

Apple18:10:09

1. test with curl

Apple18:10:26

2. check browser activity, devtools network tab.

Apple18:10:06

"hengelo" is that a valid zip code?

roelof18:10:36

yep, it is a city in the Netherlands

Apple18:10:44

curl -v '' replace xyz with your appid

Apple18:10:53

do you get valid response back?

Apple19:10:47

doesnt work here. i just register an account

roelof19:10:25

with me it works

roelof19:10:36

but still not in code

roelof19:10:25

in a browser

Apple19:10:45

of course. curl shows you the response

Apple19:10:09

perhaps encoding?

Apple19:10:59

$ curl -v -H "Accept: application/json" '' <html><head><title>Loading...</title></head><body><script type='text/javascript'>window.location.replace('

Apple19:10:13

ajax call cannot interpret this kind of response.

roelof19:10:41

oke when I try it in the browser I see a json respons

Apple19:10:16

the window.location.replace in response essentially redirects you to another location

Apple19:10:30

ajax call cannot interpret

roelof19:10:42

maybe after a good night sleep I see what is wrong here

(defn get-coordinates []
  (let [zipcode (:zipcode "Hengelo")]
    (ajax/GET ""
     {:params {"q" zipcode
               "appid" api-key}
      :handler (fn [resp] (reset! raw-data resp))})))

(comment 
  get-coordinates
  @raw-data
  )

Apple19:10:23

ha. zipcode is nil

Apple19:10:47

(:tom "tom")
nil

Apple19:10:16

(let [zipcode "Hengelo"] ...

roelof19:10:41

Thanks for the help but it's here time to sleep

Apple19:10:38

oh i got it api key is given to me but it takes time to activate. now the london example call works.

roelof04:10:51

nice to hear

roelof09:10:39

code so far

(ns ^:figwheel-hooks learn-cljs.weather
  (:require
   [goog.dom :as gdom]
   [reagent.dom :as rdom]
   [reagent.core :as r]
   [ajax.core :as ajax]))


(defonce app-state (r/atom {:title "WhichWeather"          
                            :latitude 0
                            :longtitude 0
                            :zip-code "" 
                            :temperatures {:today {:label "Today"
                                                   :value nil}
                                           :tomorrow {:label "Tomorrow"
                                                      :value nil}}}))

(defn handle-response [resp]
  (let [today (get-in resp ["hourly" 1 "temp"])
        tomorrow (get-in resp ["hourly" 5 "temp"])]
    (swap! app-state
           update-in [:temperatures :today :value] (constantly today))
    (swap! app-state
           update-in [:temperatures :tomorrow :value] (constantly tomorrow))))

(defn handle-response-coordinates[resp]
   (let [lat (get-in resp ["coord" "lat"])
         lon (get-in resp ["coord" "lon"])]
     (swap! app-state
          update-in [:latitude] (constantly lat))
     (swap! app-state
           update-in [:longtitude] (constantly lon))))

(def api-key "fa9930ab5e2a87f9770d1c90d68f9da1")


(defn get-forecast! []                                     ;; <3>
  (let [lat (:latitude @app-state)
        lon (:longtitude @app-state)]
    (ajax/GET ""
      {:params {"lat" lat
                "lon" long
                "units" "metric" ;; alternatively, use "metric"
                "appid" api-key}
       :handler handle-response})))

(defn get-coordinates []
  (let [zipcode (:zipcode @app-state)]
    (ajax/GET ""
     {:params {"q" zipcode
                "units"  "metric"
                "appid" api-key}
      :handler handle-response-coordinates})))


(defn title []
  [:h1 (:title @app-state)])

(defn temperature [temp]
  [:div {:class "temperature"}
   [:div {:class "value"}
    (:value temp)]
   [:h2 (:label temp)]])

(defn postal-code []
  [:div {:class "postal-code"}
   [:h3 "Enter your postal code"]
   [:input {:type "text"
            :placeholder "Postal Code"
            :value (:postal-code @app-state)
            :on-change #(swap! app-state assoc :postal-code (-> % .-target .-value))}]
   [:button {:on-click get-coordinates} "Go"]])

(defn app []
  [:div {:class "app"}
   [title]
   [:div {:class "temperatures"}
    (for [temp (vals (:temperatures @app-state))]
      [temperature temp])]
   [postal-code]])

(defn mount-app-element []
  (rdom/render [app] (gdom/getElement "app")))

(mount-app-element)

(defn ^:after-load on-reload []
  (mount-app-element))

Benjamin11:10:01

when calling grep with clojure.java.shell it never finds something. I have the suspicion there is some jvm reason for this? I'm trying to implement some simple bb program that involves grepping over a big directory

Benjamin11:10:41

one fix is to use git grep

👍 1
Sebastian11:10:51

I feel really stupid asking this, because the problem is so simple, but I can't wrap my head around how to solve it in clojure. I have a list of integers of which I want to return the index of the highest integer. Simple to do in oop. Any tips? I can't use higher-order functions, because they don't keep track of the indexes?

pithyless11:10:10

Some ideas: if you reduce over the collection, you can keep track of the existing index, as well as the index and value of the highest integer.

Sebastian11:10:23

Not sure how I would do that? I would at least have to manipulate the list first so each value is paired with its index first

pithyless11:10:26

Or you can use map-indexed to return a list of tuples [value index] and then e.g. sort and return the highest value

pithyless11:10:08

No, in case of reduce you are free to keep an accumulator and stuff whatever you want into it

Mno11:10:47

or another way is to turn it into 2 steps:

(.indexOf vector-of-ints (apply max vector-of-ints))

👍 2
Sebastian11:10:26

Yes but with reduce, how do i know what index I am currently looking at? If I reduce over the vector [1 4 6 8 9 2] I cannot know that the current number I am looking at is at a specific index?

Sebastian11:10:39

Unless i of course use map-indexed beforehand

Mno11:10:45

You keep the index of the current highest value and the value in a vector as the accumulator

Mno11:10:02

oh wow it's a hard reduction to do, but yeah you'd have to have the highest-value, previous-index, and highest-value-index in the accumulator.

pithyless11:10:23

yeah, something to grok what we mean:

(reduce (fn [acc n]
           (if acc
             (let [[idx max-idx max-val] acc]
               (if (> n max-val)
                 [(inc idx) idx n]
                 [(inc idx) max-idx max-val]))
             [0 0 n]))
         nil
         [1 4 6 8 9 2])

Mno11:10:09

user> (def vector-of-ints [1 2 3 5 7 2 1 10 4 20 42 4 3 1])

user> (reduce
 (fn [[index highest-val previous-index] current-val]
   (let [current-index (inc previous-index)]
     (if (> current-val highest-val)
       [current-index current-val current-index]
       [index highest-val current-index])))
 [0 0 -1]
 vector-of-ints)

;;=> [10 42 13]
user> (get vector-of-ints 10)

Mno11:10:17

yeah that was my verbose version

Ed11:10:22

the reduction is the same logic as you would write in an imperative language, but you have to put all your state in a data structure and return it from a function rather than use lexical scope to refer to variables ... so it looks confusing ... is that a useful perspective?

Ed11:10:05

You could also use a map rather than a vector if you wanted to name the "variables" in the state ;)

☝️ 1
Ed11:10:48

this was my stab at it with reduce

(:idx (reduce (fn [{:keys [mx i] :as r} x]
                  (-> r
                      (cond-> (some-> mx (< x)) (assoc :idx i :mx x))
                      (cond-> (nil? mx) (assoc :mx x))
                      (update :i (fnil inc 0))))
                nil
                [1 4 6 8 9 2]))

Ed11:10:04

not sure if that's very helpful tho

pithyless11:10:10

here's a different approach:

(let [[idx val] (->> [1 4 6 8 9 2]
                     (map-indexed vector)
                     (sort-by second >)
                     first)]
  idx)

🙌 1
Mno11:10:58

so many ways to go about this:

(first (apply max-key second (map-indexed vector '(2 1 6 5 4)))) 

Mno11:10:09

straight from the max-key docs 😅

pithyless11:10:29

Today I learned about max-key 😅

Jim Strieter12:10:26

Here's how I did it: (defn index-of-highest [some-seq] (loop [idx 0 biggest-known-number -1E100 idx-of-biggest-known -1] (if (>= idx (count some-seq)) ;; If index is beyond bounds of some-seq, return idx-of-biggest-known (if (< biggest-known-number (get some-seq idx)) (recur (+ idx 1) (get some-seq idx) idx) (recur (+ idx 1) biggest-known-number idx-of-biggest-known))))) I chose the loop syntax because it gives you the illusion of TCO in Clojure.

Jim Strieter12:10:57

@U05476190 Dude that is next level

Jim Strieter12:10:21

@U05476190 I suggest changing (range 5) to my-input-vector

👍 1
pithyless12:10:37

sure, I edited it with some more random numbers (I'd prefer something that you can eval directly in the repl)

👍 1
Ben Sless18:10:33

Transducers version 🙂

(defn- rf [acc x] (max-key peek acc x))

(defn max-idx
  [xs]
  (peek
   (reduce rf
    (->Eduction (map-indexed vector) xs))))

metal 3
Ben Sless18:10:29

Here's an "illegal" version

(defn max-idx2
  ^long [^Iterable xs]
  (let [it (.iterator xs)]
    (loop [i 0
           -max-idx i
           -max (unchecked-long (.next it))]
      (if (.hasNext it)
        (let [x (unchecked-long (.next it))
              i (unchecked-inc i)]
          (if (> x -max)
            (recur i i x)
            (recur i -max-idx -max)))
        -max-idx))))
With checked math it's ~5x faster than my max-idx With uncheck math it's ~22x faster

Ben Sless18:10:57

Checked for a case where the max element is last

Alex Appetiti21:10:34

(defn max-index
  [nums]
  (apply max-key #(nth nums %) (range (count nums))))
I’d go for something like this or @UGFL22X0Q’s two-step version Edit: Probably not very efficient for lists (vectors should be fine).

Sebastian11:10:38

Maybe a solution is to pair the integer with its index in a vector, but it seems weird?

Jim Strieter13:10:34

Several (though certainly not all) approaches that people consider FP will do that

manutter5111:10:39

The map-indexed fn might be of use here.

roelof16:10:05

no body who can help me with my clojure-script problem ?

jeffy19:10:04

Hello, am trying to figure out how to use environment values for migratus with the leiningen plugin. I followed the instructions on https://github.com/yogthos/migratus#quick-start-with-leiningen I have the database password in an environment variable. When I evaluate (environ.core/env :db-password), I get the value for it, but when I run lein migratus migrate I get,

migrating all outstanding migrations
Syntax error (ClassNotFoundException) compiling at (/private/var/folders/d_/vygvcb251gl8by5c8m1_wlsr0000gn/T/form-init9053620023813719463.clj:1:341).
environ.core

Full report at:
/var/folders/d_/vygvcb251gl8by5c8m1_wlsr0000gn/T/clojure-5134916087895186035.edn
Subprocess failed (exit code: 1)
If some one has encountered this before or has any idea why this is, I would highly appreciate the help. Thanks. p.s. my project.clj file looks like this.
(defproject
 playground "0.1.0"
 :description "Example project"
 :url ""
 :dependencies [[com.github.seancorfield/next.jdbc "1.2.731"]
                [environ "1.2.0"]
                [migratus "1.3.5"]
                [org.clojure/clojure "1.10.3"]
                [org.postgresql/postgresql "42.3.0"]]

 :min-lein-version "2.0.0"
 :main ^:skip-aot playground.core

 :plugins [[lein-environ "1.2.0"]
           [migratus-lein "0.7.3"]]

 :migratus {:store :database
            :migration-dir "migrations"
            :db {:dbtype "postgres"
                 :dbname "playground"
                 :user "playground"
                 :password (environ.core/env :db-password)
                 :port 5433}}

 :profiles
 {:uberjar
  {:aot :all
   :uberjar-name "app-standalone.jar"}

  :dev [:project/dev :profiles/dev]
  :test [:project/test :profiles/test]

  :project/dev
  {:plugins []}
  :project/test {}
  :profiles/dev {}
  :profiles/test {}})

Apple19:10:58

did you check the report?

Apple19:10:18

Full report at:
/var/folders/d_/vygvcb251gl8by5c8m1_wlsr0000gn/T/clojure-5134916087895186035.edn

jeffy19:10:26

yes and did not understand what was causing it.

{:clojure.main/message
 "Syntax error (ClassNotFoundException) compiling at (/private/var/folders/d_/vygvcb251gl8by5c8m1_wlsr0000gn/T/form-init9053620023813719463.clj:1:341).\nenviron.core\n",
 :clojure.main/triage
 {:clojure.error/phase :compile-syntax-check,
  :clojure.error/line 1,
  :clojure.error/column 341,
  :clojure.error/source "form-init9053620023813719463.clj",
  :clojure.error/path
  "/private/var/folders/d_/vygvcb251gl8by5c8m1_wlsr0000gn/T/form-init9053620023813719463.clj",
  :clojure.error/class java.lang.ClassNotFoundException,
  :clojure.error/cause "environ.core"},
 :clojure.main/trace
 {:via
  [{:type clojure.lang.Compiler$CompilerException,
    :message
    "Syntax error compiling at (/private/var/folders/d_/vygvcb251gl8by5c8m1_wlsr0000gn/T/form-init9053620023813719463.clj:1:341).",
    :data
    {:clojure.error/phase :compile-syntax-check,
     :clojure.error/line 1,
     :clojure.error/column 341,
     :clojure.error/source
     "/private/var/folders/d_/vygvcb251gl8by5c8m1_wlsr0000gn/T/form-init9053620023813719463.clj"},
    :at [clojure.lang.Compiler analyzeSeq "Compiler.java" 7119]}
   {:type java.lang.ClassNotFoundException,
    :message "environ.core",
    :at
    [java.net.URLClassLoader findClass "URLClassLoader.java" 440]}],
  :trace
  [[java.net.URLClassLoader findClass "URLClassLoader.java" 440]
   [clojure.lang.DynamicClassLoader
    findClass
    "DynamicClassLoader.java"
    69]
   [java.lang.ClassLoader loadClass "ClassLoader.java" 587]
   [clojure.lang.DynamicClassLoader
    loadClass
    "DynamicClassLoader.java"
    77]
   [java.lang.ClassLoader loadClass "ClassLoader.java" 520]
   [java.lang.Class forName0 "Class.java" -2]
   [java.lang.Class forName "Class.java" 467]
   [clojure.lang.RT classForName "RT.java" 2212]
   [clojure.lang.RT classForNameNonLoading "RT.java" 2225]
   [clojure.lang.Compiler$HostExpr maybeClass "Compiler.java" 1041]
   [clojure.lang.Compiler macroexpand1 "Compiler.java" 7049]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7097]
   [clojure.lang.Compiler analyze "Compiler.java" 6793]
   [clojure.lang.Compiler analyze "Compiler.java" 6749]
   [clojure.lang.Compiler$MapExpr parse "Compiler.java" 3108]
   [clojure.lang.Compiler analyze "Compiler.java" 6801]
   [clojure.lang.Compiler analyze "Compiler.java" 6749]
   [clojure.lang.Compiler$MapExpr parse "Compiler.java" 3108]
   [clojure.lang.Compiler analyze "Compiler.java" 6801]
   [clojure.lang.Compiler analyze "Compiler.java" 6749]
   [clojure.lang.Compiler$InvokeExpr parse "Compiler.java" 3892]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7113]
   [clojure.lang.Compiler analyze "Compiler.java" 6793]
   [clojure.lang.Compiler analyze "Compiler.java" 6749]
   [clojure.lang.Compiler$BodyExpr$Parser parse "Compiler.java" 6124]
   [clojure.lang.Compiler$FnMethod parse "Compiler.java" 5471]
   [clojure.lang.Compiler$FnExpr parse "Compiler.java" 4033]
   [clojure.lang.Compiler analyzeSeq "Compiler.java" 7109]
   [clojure.lang.Compiler analyze "Compiler.java" 6793]
   [clojure.lang.Compiler eval "Compiler.java" 7178]
   [clojure.lang.Compiler eval "Compiler.java" 7171]
   [clojure.lang.Compiler load "Compiler.java" 7640]
   [clojure.lang.Compiler loadFile "Compiler.java" 7578]
   [clojure.main$load_script invokeStatic "main.clj" 475]
   [clojure.main$init_opt invokeStatic "main.clj" 477]
   [clojure.main$init_opt invoke "main.clj" 477]
   [clojure.main$initialize invokeStatic "main.clj" 508]
   [clojure.main$null_opt invokeStatic "main.clj" 542]
   [clojure.main$null_opt invoke "main.clj" 539]
   [clojure.main$main invokeStatic "main.clj" 664]
   [clojure.main$main doInvoke "main.clj" 616]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.main main "main.java" 40]],
  :cause "environ.core",
  :phase :compile-syntax-check}}

Apple19:10:05

:type java.lang.ClassNotFoundException,
    :message "environ.core",
you dont necessarily have access to environ/core in project.clj settings

jeffy19:10:37

thanks for clarifying 🙂

Apple20:10:41

{:store :database
 :migration-dir "migrations"
 :db ~(get (System/getenv) "DATABASE_URL")}
see if this works. from the example.

Apple20:10:04

in your case probably just

:password (get (System/getenv) "DB_PASSWORD")

jeffy20:10:08

Thanks for the tip !! (get (System/getenv) "DB_PASSWORD") returns nil and lein migratus migrate gives

SEVERE: Error creating DB connection for {:dbtype "postgres", :dbname "playground", :user "playground", :password nil, :port 5433}
org.postgresql.util.PSQLException: The server requested SCRAM-based authentication, but the password is an empty string.
this is my profiles.clj
{:profiles/dev {:env {:db-password "playground"}}}
and this is .lein-env
{:db-password "playground"}

Apple20:10:21

oh this sounds like how lein setup env. but is this the same as system environment variable like you'd do with BASH/ZSH?

Apple20:10:43

(get (System/getenv) "DB_PASSWORD")
this is for the latter.

jeffy07:10:00

When I add env variable in terminal like export DB_PASSWORD=playground and use :password (get (System/getenv) "DB_PASSWORD") in project.clj, the lein command lein migratus migrate works as expected. But am not able to read env variables in project.clj which was added from profiles.clj. I am also using lein environ plugin in project.clj like

:plugins [[lein-environ "1.2.0"]
          [migratus-lein "0.7.3"]]
Adding env variable into terminal each time I need them is not an ideal solution for me. I would like to find out a way so that the env variable is available every time I start the project. And that is why I used profiles.clj and environ library with lein-environ plugin, but unfortunately environ cannot be used from project.clj @UP82LQR9N I appreciate your help in this so far 🙂

orpheus22:10:36

I took this random kotlin function from one of our projects at work (of which I don’t have context of), and tried to re-write it in Clojure (not caring if it’s exactly correct) to just try to get a feel for what it would look like. I pretty much transferred line for line of how I read it imperatively and it came out with more lines of code and a lot harder to read. I’m guessing my problem was not truly understanding the function before writing it in Clojure. I wanted to share here to see if I could get any valuable feedback of some things I could’ve done better. Kotlin:

fun AbstractEnvironment.getPropertiesByPrefix(prefix: String): Map<String, Map<String, Any?>> {
  val propertyMap = mutableMapOf<String, MutableMap<String, Any?>>()
  val environmentPrefix = prefix.toUpperCase().replace(".", "_")

  // reverse order to get correct overrides
  propertySources.reversed().forEach { source ->
    // If this is an environment variable or system property, we want to compare with the environmentPrefix
    val compareAsEnvironment = source.name == "systemEnvironment" || source.name == "systemProperties"

    (source as? EnumerablePropertySource)?.let { enumerableSource ->
      enumerableSource.propertyNames.forEach { propertyName ->
        if (propertyName.startsWith(prefix) || (compareAsEnvironment && propertyName.toUpperCase().startsWith(environmentPrefix))) {
          // If this is an env, replace the "_" with "."
          val name = if (compareAsEnvironment) propertyName.replace("_", ".") else propertyName
          val key = name.substring(prefix.length +1, name.indexOf('.', prefix.length +1))
          val map = propertyMap.getOrPut(key) { mutableMapOf() }

          map[name.substring("$prefix.$key.".length)] = enumerableSource.getProperty(name)
        }
      }
    }
  }

  return propertyMap
}
Clojure:
(defn get-properties-by-prefix [prefix property-sources]
        (let [env-prefix (-> prefix .toUpperCase (.replace "." "_"))]
          (loop [[source & rest] (reverse property-sources)
                 property-map {}]
            (if-not rest property-map)
            (let [cmpAsEnv (or (= "systemEnvironment" (:name source))
                               (= "systemProperties" (:name source)))]
              (loop [[propertyName & rest] (:propertNames source)]
                (if (or (-> propertyName (.startsWith prefix))
                        (and cmpAsEnv
                             (-> propertyName .toUpperCase (.startsWith env-prefix))))
                  (let [name (if cmpAsEnv (.replace propertyName "_" ".") propertyName)
                        key (-> name (.substring (+ 1 (.length prefix)) (.indexOf name "." (+ 1 (.length prefix)))))
                        map (key property-map {})]
                    (if rest
                      (recur rest (assoc property-map (-> name (.substring (.length (str prefix "." key "."))))
                                         (:name source))))))))
            (recur rest))))

Max Deineko10:10:47

Is my understanding correct that the kotlin version is supposed to, given prefix "uk", transform something like (using clojure literal syntax)

[{:name "systemEnvironment"
  :propertyNames ["UK_EMAIL_ADDRESS_DETAILS" "DE_EMAIL_ADDRESS" "uk.full.nick"]
  :getProperty {"UK_EMAIL_ADDRESS_DETAILS" 1 "DE_EMAIL_ADDRESS" 2 "uk.full.nick" 0}}
 {:name "somewhereElse"
  :propertyNames ["uk.email.address" "uk.full.name" "de.full.name" "UK_WAIT"]
  :getProperty {"uk.email.address" 3 "uk.full.name" 4 "de.full.name" 5 "UK_WAIT" 0}}]
into
{{"EMAIL" {"ADDRESS.DETAILS" 1}
 "email" {"address" 3}
 "full" {"name" 4 "nick" 0}}
? If so, does enumerableSource.getProperty(name) actually return anything but null in case compareAsEnvironment = true? (I'd expect to see getProperty(propertyName) there)

hiredman23:10:57

I mean, of that is just nonsense, like an if-not that doesn't do anything ever

hiredman23:10:32

The calling the key function with multiple arguments

hiredman23:10:46

That isn't clojure.core/key, that is the key bound right before it, which isn't even a function, so calling it makes even less sense

hiredman23:10:33

Like, basically, this is completely broken, and not comparable to anything

phronmophobic00:10:59

I think that's an unfair treatment. To me, even if it can't be run, it does show that merely translating an imperative function from Java/Kotlin to clojure won't usually yield good results. That's an insight with its own merit!

🖤 1
phronmophobic00:10:25

It also leads to more good questions like: • what would an idiomatic translation look like? • why is the imperative version more verbose in clojure when clojure is typically known for its succinctness? • does that mean algorithms that require imperative side-effects are typically more verbose in clojure?

hiredman00:10:17

It isn't even a good translation of the imperative

hiredman00:10:15

E.g. it doesn't use doseq for the looping

hiredman00:10:12

And like is still building a map via assoc

phronmophobic00:10:19

I think the if-not is supposed to also include the let as the else branch.

phronmophobic00:10:45

so then it would be a loop that accumulates into property-map

hiredman00:10:07

How is a loop with an immutable map accumulator imperative?

phronmophobic00:10:50

I never said it was.

phronmophobic00:10:41

it's an imperative translation.

phronmophobic00:10:33

I guess my main point is that since this is the #beginners channel, we can give better feedback than "this is completely broken", which I don't think is a fair assessment.

hiredman00:10:29

They are trying to compare it to some kotlin code, if we are going to allow comparisons of completely broke code, at what point does one draw the line?

hiredman00:10:16

E.g. is nil a valid translation? Does it show how great clojure is? I mean it's so small

hiredman00:10:25

If the given translation is acceptable for comparisons, to the point where you are trying to draw conclusions about verbosity, and nil isn't acceptable, where is the line?

orpheus00:10:33

Hmm okay, use doseq and don’t use assoc for building maps, thank you!

phronmophobic00:10:28

> They are trying to compare it to some kotlin code, if we are going to allow comparisons of completely broke code, at what point does one draw the line? I think any honest attempt is good enough to learn from. If nil was the best attempt someone could produce, I would still try to offer help (probably by pointing them to better self-starting resources). Using negative superlatives is the absolute worst kind of feedback :p

orpheus01:10:46

Hey thanks for understanding phron. Still trying to grasp the difference in paradigms and how I would go about translating from one to another. I’ll try again tomorrow with a different function that I do understand

👍 1
hiredman02:10:10

My comments about doseq, assoc, etc are all aimed at the suggestion that this code is imperative

👍 1
hiredman02:10:18

Written idiomatically, the code I would likely be some kind of reduce building a map, maybe a nested map using assoc-in, hard to tell

hiredman02:10:59

A straight forward imperative translation of the kotlin would do things like use iterators, because kotlins foreach method is sugar over iterators

hiredman02:10:20

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/for-each.html has an example of what using a raw iterator looks like vs a kotlin foreach (same number of lines amusingly)

didibus02:10:08

I will say since there is a paradigm shift, translating function can be a bad way to get a feel for the different paradigm. It might be better you start with the problem and try to find out how to implement it in Clojure instead, doing so with little inspiration from the original or it might even lead you down the wrong path.

didibus02:10:07

I'd recommend you work from a test instead. Like write the input and the output you expect. And then work at the REPL to try and find a way in Clojure given that input to return the desired output.

1
didibus02:10:13

But also if I look at this Kotlin, the challenge is that in Clojure you would not have the data modeled the way it is in Kotlin either, so the feel of using Clojure would differ even in the shape and structure you'd be operating over.

didibus03:10:36

I tried to reverse engineer what the Kotlin does, but I'm just too confused about what the original data looks like and all the weird string manipulation happening. Sorry, even after 15 minutes of looking at it.

didibus03:10:29

I'm seeing that there are sources that have properties, and they can start with a prefix indicating if they are a type of env, and then I start to get lost haha