Fork me on GitHub
#beginners
<
2020-04-23
>
Alex Miller (Clojure team)00:04:47

Or you could just make the function to call configurable and not call it at all

em00:04:25

What would be an idiomatic way to do memoization-type dynamic programming in Clojure, where it seems like mutability would help? For example the coding example on https://www.dailycodingproblem.com/

There's a staircase with N steps, and you can climb 1 or 2 steps at a time. Given N, write a function that returns the number of unique ways you can climb the staircase. The order of the steps matters.

For example, if N is 4, then there are 5 unique ways:

1, 1, 1, 1
2, 1, 1
1, 2, 1
1, 1, 2
2, 2
What if, instead of being able to climb 1 or 2 steps at a time, you could climb any number from a set of positive integers X? For example, if X = {1, 3, 5}, you could climb 1, 3, or 5 steps at a time. Generalize your function to take in X.

em00:04:48

The python solution is pretty crisp:

def staircase(n, X):
    cache = [0 for _ in range(n + 1)]
    cache[0] = 1
    for i in range(1, n + 1):
        cache[i] += sum(cache[i - x] for x in X if i - x >= 0)
    return cache[n]

noisesmith00:04:04

there's clojure.core/memoize

em00:04:21

I had a go using sequences and reduce, but not sure if it's more or less readable

(defn staircase [n segments]
  (letfn [(valid-paths [cache i]
            (->> (map - (repeat i) segments)
                 (filter #(<= 0 %))
                 (map cache)
                 (reduce +)
                 (conj cache)))]
    (last (reduce valid-paths [1] (range 1 (inc n))))))

em00:04:06

@noisesmith Yeah I know about memoize, but you'd need to make it a function call, whereas here the technique (or at least python equiv) is just a simple mutable vector without the overhead of recursion

noisesmith00:04:15

actually, to me that staircase function from python looks precisely like a lazy-seq

em00:04:55

That was exactly my first thought, but then you'd get O(n) lookup performance when you needed to reference the earlier elements for building each new one

noisesmith00:04:21

but you always read them start to end in order anyway

noisesmith00:04:59

one moment, I'll try a lazy-seq version that uses an accumulator instead of linear walks...

andy.fingerhut00:04:38

Many dynamic programming techniques are effectively like memoization, but they fill in some kind of a vector, 2d array, etc. in a particular order, such that whenever you are calculating the value to fill in one entry, all subproblems have been calculated earlier, and can be retrieved from the saved vector/2d-array/etc.

andy.fingerhut00:04:23

In such a case, doing any kind of loop/reduce/etc. where one of the loop values (or values accumulated from one reduce step to another), is that vector/2d-array/etc.

andy.fingerhut00:04:05

That need not be considered mutation any more than the rebinding of a loop variable's value from one iteration to the next is (which means, not mutation at all, except perhaps in the underlying machine code implementation)

em00:04:49

@U0CMVHBL2 Thanks, makes a lot of sense! I settled on the reduce implementation on similar thinking, though it did seem that the machinery around needing to setup a function/direct the flow of implicit returns sometimes ends up being more verbose than a side-effecting python assignment. Was just wondering if there were more elegant approaches to tackle this domain of problems

andy.fingerhut00:04:24

I have not looked at the particular Python solution proposed, but a single mutating assignment to a Python dict can become a single call to assoc on a Clojure map, for example

dpsutton02:04:39

(defn steps-dynamic [n]
  (let [cache (merge (zipmap (range (inc n)) (repeat 0))
                     {0 1})
        cache' (reduce (fn [cache n']
                         (update cache n' + (apply + (vals (select-keys cache (take 2 (reverse (range n'))))))))
                       cache
                       (rest (range (inc n))))]
    (get cache' n)))

(defn steps-tail-recursive [n]
  (letfn [(iter [x prev prev']
            (if (= x n)
              (+ prev prev')
              (recur (inc x) (+ prev prev') prev')))]
    (iter 0 0 1)))

(defn steps-recursive [n]
  (cond (< n 0) 0
        (zero? n) 1
        :else (+ (steps-recursive (dec n))
                 (steps-recursive (dec (dec n))))))

noisesmith02:04:56

aren't those missing the X arg?

dpsutton02:04:26

i got confused about that but i think X is #{1,2}

dpsutton02:04:56

for "climb 1 or 2 steps at a time" but i'm honestly not sure what X is otherwise

noisesmith02:04:08

> What if, instead of being able to climb 1 or 2 steps at a time, you could climb any number from a set of positive integers X? For example, if X = {1, 3, 5}, you could climb 1, 3, or 5 steps at a time. Generalize your function to take in X.

noisesmith02:04:11

from the link

👍 4
noisesmith02:04:34

seems like you could use for with X as an inner comprehension to get every combination of steps

em05:04:01

My solution renamed X to segments, but yeah, I tried the for loop approach initially to mirror the python code but it was pretty unwieldy to slot into a large reduce, ended up going all sequence functions. Would be cool to see a really crisp clojure implementation that runs just as fast

em06:04:36

@U11BV7MTK Those look nice for the #{1 2} case! Though that particular case does devolve exactly into the Fibonacci sequence

didibus08:04:57

You can just use a cache the same way. That's how memorize is implemented anyways

Ben Sless09:04:04

Generally, a naive and non optimal solution would be (this is for 1,2,3 steps):

(defn ways
  [n]
  (cond
    (< n 0) 0
    (= n 0) 1
    :else (+ (ways (- n 1))
             (ways (- n 2))
             (ways (- n 3)))))
You can trivially cache it with memoize:
(declare ways)
(declare ways*)
(def ways (memoize ways*))
(defn ways*
  [n]
  (cond
    (< n 0) 0
    (= n 0) 1
    :else (+ (ways (- n 1))
             (ways (- n 2))
             (ways (- n 3)))))
Both of these are easy to generalize to any number and size of steps If you want to do it with tail recursion, you can create a sliding binding:
(defn dways
  [n]
  (cond
    (<= n 0) 0
    (= n 1) 1
    (= n 2) 2
    (= n 3) 4
    :else
    (loop
        [a 1, b 2, c 4, i 3]
      (if (< i n)
        (recur b c (+ a b c) (inc i))
        c))))
I think if you want to parametrise that over the possible steps you might need a macro

tjb03:04:29

has anyone had experience with Ring to inspect an incoming request? i am able to get the body but it is in an input stream and i would like it in a map. essentially what im trying to do is intercept an incoming request to do a type conversion if an ID is present

phronmophobic04:04:19

intercept a request between what and where? if you have an input stream, you can use slurp to read the full inputstream into a string, but you’ll need to parse the string if you want a map.

tjb04:04:55

so my angular app is making a POST request. when the request hits the server i want to intercept this request before it hits my controller / routes in order to do the modification

tjb04:04:00

does that make sense?

tjb04:04:15

or maybe this is not possible

tjb04:04:22

and i have to do the modification in each route

phronmophobic04:04:22

this sounds like a job for middleware!

tjb04:04:10

correct i am trying to create one but i am unsure where

tjb04:04:34

at this level i get the :body as an input stream which i am unsure if this is the right place

tjb04:04:36

(defn wrap-database-component [handler database]
  (fn [req]
    (handler (assoc req :database database))))

(defn wrap-uuid-type [handler]
  (fn [req]
    (handler req)))

(defn routes-handler [database]
  (-> routes/define-main-routes
      (wrap-database-component database)
      (wrap-uuid-type)))

phronmophobic04:04:28

seems reasonable

tjb04:04:38

wrap-uuid-type is what im trying to create. i have come across (`request/body-string)` ring util but since it is just a string i dont think it makes sense to do that. would i convert the input stream here into a map and modify the type then?

tjb04:04:57

does it have to be converted back to an input stream? i am unsure and maybe trial and error is needed

tjb04:04:54

let me see...

phronmophobic04:04:12

typically, you just add more data to the request map to help later handlers. something like:

(defn wrap-uuid-type [handler]
  (fn [req]
    (handler (assoc req :uuid (make-uuid)))))

tjb04:04:18

right i was hoping i didnt have to push the logic down more cause then i would have to copy-pasta for each route to do the modification on the :id key

phronmophobic04:04:40

shouldn’t it add the uuid to the request route for every handler/route in define-main-routes?

tjb04:04:07

well i dont want to add a key called :uuid i want to convert the id key in the incoming json to type of uuid

phronmophobic04:04:19

well, there’s two options: 1. add a new key of uuid that is the conversion:

(defn wrap-uuid-type [handler]
  (fn [req]
    (handler (assoc req :uuid (extract-uuid (:body req)))))
2. replace id in the actual body
(defn wrap-uuid-type [handler]
  (fn [req]
    (handler (assoc req :body (convert-uuid-in-body (:body req)))))
I would typically recommend the first, but there’s still a lot I don’t know about the use case. is the response body json or some other format?

tjb04:04:56

option 2 is what im attempt but intercepting the incoming POST request body to make the conversion seems to be way more problematic than i thought

tjb04:04:20

the response body is json

phronmophobic04:04:40

do you have middle ware that parses the json?

tjb04:04:07

that seems to work out of the box with ring

tjb04:04:10

for incoming requests

tjb04:04:16

my responses are wrapped with this middleware

tjb04:04:22

(defn wrap-routes
  [routes]
  (-> routes
      (wrap-json-to-camel-case)
      (wrap-json-response)
      (wrap-json-body {:keywords? true})
      (wrap-params)
      (wrap-multipart-params)
      (wrap-cors :access-control-allow-origin [#""]
                 :access-control-allow-methods [:get :put :post :delete])))

tjb04:04:41

let me know if i am not making sense haha

phronmophobic04:04:49

you’ll probably want your conversion middleware after the wrap-json* stuff

tjb04:04:54

im pretty bad at trying to take what is in my head to paper

tjb04:04:04

but ^ only fires when i do a response

tjb04:04:09

not on the incoming request

tjb04:04:13

i think?

tjb04:04:41

im pretty sure. let me triple check

tjb04:04:58

my understanding is there are two middleware locations: for incoming requests and outgoing responses

tjb04:04:02

im so stupid

tjb04:04:09

thank you so much @smith.adriane

tjb04:04:14

i was in the wrong place the WHOLE time loL!

4
tjb04:04:38

actually i guess they are the "same"

tjb04:04:47

not sure why i have two different places :thinking_face:

phronmophobic04:04:03

it’s been a while since I’ve messed with middleware, but I think you can wrap before, after or both with any middleware

phronmophobic04:04:56

interceptors are a popular alternative to middleware and I think interceptors are opinionated about before and after

tjb04:04:04

the body still seems to be a stream at this point which i hope the wrapper would have converted it

tjb04:04:05

:body #object[org.eclipse.jetty.server.HttpInputOverHTTP 0x1a637ea3 HttpInputOverHTTP@1a637ea3[c=0,q=0,[0]=null,s=STREAM]]

phronmophobic04:04:57

it seems to only replace the body if the content-type header matches #"^application/(.+\+)?json"

tjb04:04:33

correct i just checked the docs and checked the request and the content-type is there

phronmophobic04:04:03

and the body is converted to json by the time it hits a normal handler later?

tjb04:04:29

triple verifying

phronmophobic04:04:29

i’m not sure where wrap-uuid-type is specified in your middleware list, but I would try it right before wrap-json-body and if that doesn’t work, try right after

phronmophobic04:04:41

i always forget which order things need to go in 😬

tjb04:04:46

i tried after the wrap-json-body and no dice let me see before

tjb04:04:53

cause this is how it looks in my normal handler

tjb04:04:03

:body {:id c0c03eff-9870-4c0e-83ac-506bf59d69cf, :first Yusuke, :last Urameshi, :email }

tjb04:04:11

ok it works before!

tjb04:04:20

im pretty lost why it does that

tjb04:04:45

i thought -> threads first?

tjb04:04:49

and goes in order

tjb04:04:17

nope it does not LOL

tjb04:04:28

man im so newb at this

phronmophobic04:04:38

it’s super counter intuitive

tjb04:04:03

totally!

tjb04:04:12

i wonder what is the reasoning behind that

phronmophobic04:04:19

because -> does go in order, but something about how the wrapping works makes it go in the other order

phronmophobic04:04:54

i’ve reasoned through it before, but I usually just try one and then try the other if it doesn’t work

tjb04:04:56

ohhh shit

tjb04:04:58

i got it now

tjb04:04:03

the docs give a good visual

phronmophobic04:04:49

but there’s one extra level because you’re creating functions that return functions that call the next function

phronmophobic04:04:00

it’s the same thing with transducers

tjb04:04:19

this is a mind trip

tjb04:04:28

i feel my brain growing

phronmophobic04:04:10

like if you do (-> 10 (+ 1) (* 2)) you get 22

tjb04:04:08

im back to being confused lol

tjb04:04:15

cause this does (10 + 1)

tjb04:04:18

then 11 * 2

tjb04:04:39

but thats the opposite of what is happening in my thread macro

phronmophobic04:04:28

the thread macro is doing the same thing

phronmophobic04:04:27

(defn wrap-uuid-type [handler]
  (fn [req]
    (handler (assoc req :body (convert-uuid-in-body (:body req)))))
so if you’re doing (-> (wrap-json) (wrap-uuid-type))

phronmophobic04:04:48

wrap-uuid is creating a function that’s going to call wrap-json

phronmophobic04:04:20

so wrap-uuid is the “outside” wrap

phronmophobic04:04:33

and it will call wrap-json which will call the next inner layer

tjb04:04:14

visually it would look like (wrap-uuid-type (wrap-json)) ?

tjb04:04:31

thats the opposite of the docs it seems like no?

tjb04:04:37

;; Arguably a bit cumbersome to read:
user=> (first (.split (.replace (.toUpperCase "a b c d") "A" "X") " "))
"X"

;; Perhaps easier to read:
user=> (-> "a b c d" 
           .toUpperCase 
           (.replace "A" "X") 
           (.split " ") 
           first)

tjb04:04:04

maybe docs are wrong?

phronmophobic04:04:11

the docs are right

tjb04:04:21

cause if it was (wrap-json (wrap-uuid-type)) that doesnt make sense

tjb04:04:35

cause the json has to be wrapped before the wrap-uuid takes place

phronmophobic04:04:06

remember, there’s 4 functions

phronmophobic04:04:29

1. wrap-json, 2. wrap-json’s inner function. 3. wrap-uuid-type and 4. wrap-uuid-type’s inner function

phronmophobic04:04:50

so wrap-json gets called first, but wrap json’s inner function gets called last

phronmophobic04:04:42

and it’s worth noting the inner function doesn’t get called when you create the route, but the inner functions get called every time a request comes in

phronmophobic04:04:08

i’ve been told that I’m not good at explaining things so don’t feel bad if I’m not making any sense

tjb04:04:09

this is wild and im still confused however i have made a note about this to do some debugging to better understand it

tjb04:04:17

no you are doing an awesome job

tjb04:04:22

and helped me figure out my problem 🙂

bananadance 4
tjb04:04:50

thank you for taking time out of your day to rubber duck with me and share your knowledge with me

👍 4
tjb03:04:51

i noticed ring has this helper method request/body-string but thats about it 😞

tjb05:04:37

bubbling this to the top but big s/o to @smith.adriane in the thread above. thank you again and thank you for this community!

Phil Hunt11:04:46

Hi, I'm trying to use http://clojure.java.io/resource to work with a file. The file is definitely there because I can slurp it directly, but when I try to use (def something (io/resource "filename")) it evaluates to nil.

hindol11:04:41

Did you verify the file is in your classpath? Slurp can work on any file I think

Phil Hunt11:04:14

slurp works fine, it's the io.resource bitt hat's not working

hindol11:04:44

Yes, I meant slurp can possible pick the file up even when it's not in classpath.

Phil Hunt11:04:21

so I'm failing to understand something about classpaths. Common Lisp was so much less confusing 🙂

manutter5111:04:41

If it’s in the resources folder it should be in the classpath though

Phil Hunt11:04:31

do I need to put http://clojure.java.io in my deps.edn and restart the REPL or something?

Phil Hunt11:04:09

Or maybe go read more about Java classpaths 🙂

hindol11:04:11

A classpath is kind of like the Linux path. Just like an executable in PATH is magically found, Java will find and access code/resources from the classpath.

hindol11:04:38

No, http://clojure.java.io is part of core. It will come along with Clojure.

hindol11:04:27

So, the deps.edn file will be used to construct the classpath.

hindol11:04:39

All the dependencies will be in the classpath.

hindol11:04:12

You can use this: https://github.com/clojure/java.classpath as a dev dependency and print out the classpath.

Phil Hunt11:04:15

OK so my issue is there someplace

hindol11:04:27

Alternatively, just build the uberjar and see what's inside.

manutter5111:04:24

Yeah looks like your resources directory isn’t getting added to the classpath somehow, I’d have expected that to be a default (but I’m mostly using leiningen, not really up to speed on deps stuff)

Phil Hunt11:04:27

;; debugging classpath issue with io/resource (require '[clojure.java.classpath :as cp]) (cp/classpath)

hindol11:04:11

Can you share your deps.edn?

Phil Hunt11:04:26

{:paths ["src" "test" "resources"] :deps {org.clojure/clojure {:mvn/version "1.10.1"} metasoarous/oz {:mvn/version "1.6.0-alpha6"} org.clojure/data.csv {:mvn/version "1.0.0"} semantic-csv {:mvn "0.2.1-alpha1"} }

hindol11:04:03

Did you start your REPL in the same directory as the deps.edn?

hindol11:04:17

Because I just tried, and I get this,

(#object[.File 0x32aa7f46 "dev"]
 #object[.File 0x74fb4367 "classes"]
 #object[.File 0x5f17bffe "src"]
 #object[.File 0x704b5a50 "resources"]
...

Phil Hunt11:04:48

just restarting everything

Phil Hunt11:04:37

there was an issue with that semantic-csv dependency

Phil Hunt11:04:21

still fiddling, but thanks for the help so far

Phil Hunt11:04:07

Do you get a lot of people struggling with the Java underneath? I find it quite disorienting

manutter5111:04:28

If Clojure is their first exposure to Java, yeah, people do have struggles sometimes

manutter5111:04:03

Of course, they have the same struggles if Java is their first exposure to Java. It’s just more expected there. 😉

😅 4
Phil Hunt11:04:59

OK sorted thanks guys

Phil Hunt11:04:10

it was likely something to do with the bad dependency

Phil Hunt11:04:20

I guess the horrible debug experience compared to Common Lisp is the price paid for interop.

Phil Hunt11:04:42

Probably worth it if I want anyone to let me do this in prod though 🙂

Phil Hunt11:04:19

Thanks for your patience guys 🙂

manutter5111:04:22

It might be a problem with the relative path, is it inside a resources folder in your project root?

manutter5111:04:08

what relative path are you using?

Phil Hunt11:04:04

path is /resources/data/myfile

manutter5111:04:56

Ok, so slurp will take a path like resources/data/myfile, but io/resource just takes data/myfile (iirc)

Phil Hunt11:04:02

I'm doing (require '[http://clojure.java.io :as io]) then (io/resource "data/myvile)

Phil Hunt11:04:27

yeah, if I add the resources bit I can slurp just fine

manutter5111:04:34

Hmm, ok, that should work, I think, let me double check my own code

Phil Hunt11:04:43

but io/resource just gives me nil

Ory Band12:04:09

hi. is there a dedicated channel for all online meetups that might be happenning for clojure here? #remote-meetup history is empty

👍 4
Ory Band15:04:10

No. There are some for local meetups like #clojure-uk

Joshua13:04:19

If I wanted to build a REST API what's the go to technology? I've worked out that ring is the defacto choice for HTTP but I still need to pick things on top of it. There's a list in the wiki but most are outdated. Compojure seems relevant: https://github.com/ring-clojure/ring/wiki/Third-Party-Libraries

jtth15:04:08

Luminus has a template that implements swagger out of the box. https://luminusweb.com/docs/services.html. I’m learning that this kind of thing seems to be what Clojure’s web stacks are aimed at, and less at routine CRUD + progressively enhanced server-side rendering stuff that would probably be better off done with rails.

Joshua16:04:18

Thanks. Luminus looks like it has a lot of answers for most web development needs... quite a few steps above these other libraries. I might keep it simple and stick to the reitit and find a problem to solve then build on things from there

practicalli-johnny09:04:52

https://github.com/metosin/compojure-api is a project template for building an API in Clojure

Joshua13:04:31

but I've also come across Pedestal

Joshua13:04:29

What else is relevant? How can I find relevant libraries? I've found https://www.clojure-toolbox.com/ but the categories are wide

manutter5114:04:51

I think if you’re familiar with compojure you’ve got a good basis for looking at the other alternatives. I’m using reitit right now, and I like it, but it hasn’t been around as long as compojure

manutter5114:04:44

On the plus side, it does have built-in support for swagger, which could be helpful in building a REST API.

Joshua14:04:06

Thanks -- is knowing or finding these just a matter of experience?

Joshua14:04:22

You ask, you pay attention, look at what others are using, etc?

Jose Ferreira15:04:24

I'm trying to create a datascript connection, but i always get the error no namespace d.

Jose Ferreira15:04:31

What am i doing wrong here....

Jose Ferreira15:04:35

(ns cs50-proj1.core
  (:require [ring.adapter.jetty :as webserver]
             [ring.handler.dump :refer [handle-dump]]
             [ring.util.response :refer [response]]
             [ring.middleware.reload :refer [wrap-reload]]
             [compojure.core :refer [defroutes GET]]
             [compojure.route :refer [not-found]]
             [datascript.core :as d]))

(def conn (d/create-conn))

noisesmith15:04:15

how are you loading the namespace? have you reloaded since adding the datascript dependency?

Jose Ferreira15:04:35

i just added it as a dependency in my http://project.cl

Jose Ferreira15:04:51

(defproject cs50-proj1 "0.1.0-SNAPSHOT"
  :description "a book database exercise from CS50"
  :url ""
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url ""}
  :dependencies [[org.clojure/clojure "1.10.1"]
                 [ring "1.8.0"]
                 [compojure "1.6.1"]
                 [hiccup "1.0.5"]
                 [datascript "0.18.11"]]
  :repl-options {:init-ns cs50-proj1.core}
  :main cs50-proj1.core
  :profiles {:dev
             {:main cs50-proj1.core/-dev-main}})

noisesmith15:04:06

you need to restart your entire repl if it's new in project.clj

noisesmith15:04:21

(and reloading the namespace should hve errored)

noisesmith15:04:59

project.clj "doesn't exist" in a repl - it's a config that decides (among other things) how a repl is started

👍 4
Jose Ferreira15:04:16

ok, thats the main keypoint...(how its started)

Jose Ferreira15:04:45

any changes i make in the http://project.cl

noisesmith15:04:58

not just reload - restart

noisesmith15:04:05

I only clarify that because there's things like (require 'some.ns :reload) and (wrap-reload) that are about seeing new changes to your own project source files, but those don't make project.clj changes visible

seancorfield16:04:18

@jose.ferreira.ptm2 There are tools that let you load new dependencies into an already running REPL so you can avoid that restart -- but they are not exactly beginner-level tools and have several caveats.

seancorfield16:04:00

If you use Boot, you'll find that it has built-in functionality to load new dependencies into its REPL. That functionality is also available for CLI/`deps.edn` but relies on a branch of tools.deps.alpha (this is what I use for adding new deps while I'm working). I expect there are plugins for Leiningen, or some code you find online that would expose the dependency-loading machinery, so you could also do it with lein repl but I haven't used Leiningen for years now so I'm not up to date on the latest options there.

nick16:04:29

I have recently found https://github.com/igrishaev/etaoin Pure Clojure Webdriver protocol implementation and it just blew my mind. I always hated writing(and maintaining) Phantomjs/Selenium tests because it is rather hard to write & debug them in traditional(non-lisp) languages. But having real-time access to drive such specs from IDE/REPL is so much easier. The higher the abstraction level of spec the more benefit you get from REPL driven development. Clojure definitely sparks joy

seancorfield16:04:09

We went with clj-webdriver because that was the most mature years ago when we started writing "browser-level" tests but it's no longer maintained so I've looked at etaoin a couple of times and it does look really good.

seancorfield16:04:02

At this point tho', our front end team maintain their own tests and everything's in JS there, and our webdriver test suite is shrinking as we convert legacy apps to JS/Clojure apps (and so the front end team takes on more of that work).

seancorfield16:04:56

We also use HtmlUnit for one application and drive that from Clojure. I haven't spent much time with those tests but, on the surface, etaoin looks much nicer.

nick16:04:41

I hope the author of etaoin wouldn't lose the interest in maintaining it because all those "bug reports" in Github issues could be really discouraging. For some reason the author ignored donation offers https://github.com/igrishaev/etaoin/issues/183

seancorfield16:04:12

Yeah, that's always a risk with projects such as this -- and often it's not so much a loss of interest but more often employment changes and the maintainer ends up using a different tech instead.

seancorfield16:04:07

I have given up maintaining a number of projects in the tech I used before Clojure. I maintained them for a few years after I moved on, but it gets harder and harder when you're no longer a user of your own projects.

seancorfield16:04:29

It's how I ended up maintaining CongoMongo, clj-time, and HoneySQL -- the original maintainers moved on but I was using those projects and had a vested interest in maintaining them. Then we stopped using MongoDB at work so I stepped away from CongoMongo. Joda Time (what clj-time is based on) has been deprecated so clj-time is also deprecated (so my maintenance work there is nominal, and we're actively moving away from clj-time at work).

Jose Ferreira16:04:33

@seancorfield I'm using leiningen for now. It's good to know that there are ways to 'hot reload' deps, but for now, it's really not a problem for me. I'll keep things slow to not get to much overwhelmed...^^

Jose Ferreira16:04:30

I've noted that the community seems to be parting from Leiningen in favor of CLI

Jose Ferreira16:04:14

do you feel this is the case? should i start using CLI as well? are they exact matches, or is there an advantage to lein in some cases?

seancorfield16:04:55

I went from lein to boot (2015) to CLI/`deps.edn` (2018) and I prefer the simplicity of the latter. lein is definitely easy but it is not simple. Given that CLI/`deps.edn` is the official offering for running Clojure programs, and is well-documented on http://clojure.org and well-supported, I think anyone new to Clojure should learn it.

seancorfield16:04:43

But the vast majority to books and tutorials will remain based on Leiningen for a long time since it was the first tool for running Clojure.

Jose Ferreira16:04:24

I'll take a look at CLI/deeps.edn for the next project i start

Jose Ferreira16:04:17

if its the official way, eventually everything will gravitate towards that

chaow17:04:58

Could anyone help me print the error message from the try catch block on the else clause?

(defn h [] 
 (if-let [k (try (/ 1 0) (catch Exception e   nil))] 
   (+ k 1) 
    e))
currently if i try to run this bit i get Unable to resolve symbol: e in this context, which i guess is expected since the namescope would be in the try block

noisesmith17:04:21

e is only accessable inside the catch block, maybe you want to return it, or wrap the entire body and not just the division and return e there

noisesmith17:04:30

for example (try (+ (/ 1 0) 1) (catch Exception e e)) (where I assume in real code (/ 1 0) is something more interesting using local bindings)

noisesmith17:04:04

or (let [k (try (/ 1 0) (catch Exception e e))] (if (number? k) (+ k 1) k)

chaow17:04:18

thanks, i think iw as too fixated on using if-let

PB19:04:48

Clojure doesn't attach metadata to fn calls so that I could see the caller does it?

noisesmith19:04:12

no, but you can look at the call stack

user=> (seq (.getStackTrace (Thread/currentThread)))
([java.lang.Thread getStackTrace "Thread.java" 1606] [user$eval147 invokeStatic "NO_SOURCE_FILE" 1] [user$eval147 invoke "NO_SOURCE_FILE" 1] [clojure.lang.Compiler eval "Compiler.java" 7177] [clojure.lang.Compiler eval "Compiler.java" 7132] [clojure.core$eval invokeStatic "core.clj" 3214] [clojure.core$eval invoke "core.clj" 3210] [clojure.main$repl$read_eval_print__9086$fn__9089 invoke "main.clj" 437] [clojure.main$repl$read_eval_print__9086 invoke "main.clj" 437] [clojure.main$repl$fn__9095 invoke "main.clj" 458] [clojure.main$repl invokeStatic "main.clj" 458] [clojure.main$repl_opt invokeStatic "main.clj" 522] [clojure.main$main invokeStatic "main.clj" 667] [clojure.main$main doInvoke "main.clj" 616] [clojure.lang.RestFn invoke "RestFn.java" 397] [clojure.lang.AFn applyToHelper "AFn.java" 152] [clojure.lang.RestFn applyTo "RestFn.java" 132] [clojure.lang.Var applyTo "Var.java" 705] [clojure.main main "main.java" 40])

👍 4
PB19:04:53

I am debugging something and was hoping to use the metadata to determine the callers

nikolavojicic19:04:23

Callers can attach metadata using with-meta

PB19:04:35

That's a good idea, thanks

Joe19:04:57

Is https://github.com/cognitect-labs/test-runner the best way to run tests (using clojure.test) if you're using deps.edn?

salam19:04:57

it's one option. another option that you can consider is https://github.com/lambdaisland/kaocha

👍 4
noisesmith19:04:59

what specific object do you have in mind, when you talk about attaching metadata? a function call evaluates to some result, which may or may not be able to support metadata being attached

PB20:04:34

Yeah it needs to extend IObj

PB20:04:45

Do you think the thread will contain the callstack?

noisesmith20:04:50

I don't understand the mechanics you are looking for either - would it be adding metadata to a function argument, to a function return value?

noisesmith20:04:01

the thread always knows the current call stack, as I demonstrated above

noisesmith20:04:32

you'd need to do a bit of parsing / interpreting to get the real info, but it's all there

noisesmith20:04:15

(if the thread didn't know the call stack, your function wouldn't be able to return to its caller)

andy.fingerhut19:04:47

Mostly the things you can add metadata to are collections or symbols. (More precisely, any objects that implement the IObj interface, if I recall correctly, but most of those are the Clojure collections and symbols. Metadata cannot be added to much else, but that may be all you need.)

uwo20:04:23

Trying to squash a reflection warning on .getTimeInMillis (from java.util.Calendar) but adding a type hint isn't allowed because obviously Can't type hint a local with a primitive initializer. Any suggestions for getting rid of the reflection?

noisesmith20:04:01

what did you put the hint on?

uwo20:04:23

the local var bound to the calendar value

nikolavojicic20:04:19

You should paste that code with hint

uwo20:04:27

Sure thing. thanks:

noisesmith20:04:32

that should be fine

kcmds)user=>(#(.getTimeInMillis %) (java.util.Calendar/getInstance))
Reflection warning, NO_SOURCE_PATH:1:3 - reference to field getTimeInMillis can't be resolved.
1587673121787
(ins)user=> (#(.getTimeInMillis ^java.util.Calendar %) (java.util.Calendar/getInstance))
1587673131884

uwo20:04:32

Whoops. sorry. it was messed up the in the first paste

noisesmith20:04:54

that error should go with a primitive, I don't see how it would apply to what you are doing there

uwo20:04:44

when I attempt to eval a function in my buffer it throws with that error message:

uwo20:04:28

while it evals fine without the java.util.Calendar hint

noisesmith20:04:56

are you sure the error isn't related to the Long hint?

noisesmith20:04:22

that's the only hint I see there that clojure would derive a primitive from, and the compiler claims the error is related to a primitive

uwo20:04:36

Well now that's interesting. It will compile if I remove either of those type hints. So it works fine if only the ^Long type hint is there. Same with ^Calendar. Oh well, since ^Long can be inferred I'll remove it. Strange that it wouldn't throw that exception when only the Long hint is there though, no??

noisesmith20:04:35

it could be that it would prefer to compile a primitive long rather than a boxed Long so the inference on the method and your annotation are in conflict

uwo20:04:31

Interesting! Thanks @noisesmith!!

nikolavojicic20:04:46

try

(let [time ... extract and hint Calendar
      msec ... get millis and hint])

noisesmith20:04:36

the hint would cause a runtime crash if the types didn't match, but no compilation error

uwo20:04:13

@U011LEAEURE Thanks for the suggestion, but the issue is at eval time, not run time. The entire form fails to evaluate if both hints are present, and works if just one or the other is present.

uwo20:04:18

Just so y'all don't think I'm crazy 😂 :

nikolavojicic20:04:05

You dont need ^Long hint since .getTimeInMillis returns primitive long... compiler knows that because you specified ^java.util.Calendar

🙏 4
💯 4
uwo20:04:27

Thanks, yeah. I've read that SO entry. The thing I couldn't figure is that it doesn't complain about ^Long by itself unless ^Calendar is also there. But @noisesmith gave a good suggestion on that front. Tks!

uwo20:04:16

Feels like a "doh" moment. I should have realized this facepalm Thanks again, to both of you.

nikolavojicic20:04:05

I've never had this problem... Not hinting much 😛

Kurt Sys20:04:00

I'm having a go-loop which works fine, and which has an and-condition. However, when I run from the command line clj -m hosa , the process doesn't terminate:

(defn start-capturing [message-ch]
  (let [...
    (go-loop [wait-seconds (rand-int 5)]
      (let [action (alt!
                     (timeout (* 1000 wait-seconds)) :timeout
                     message-ch ([m] m))]
        (let [...
          ...
          (if (= action :stop)
            (do (println "stop!")
                (async/close! message-ch))
            (recur (rand-int 5)))))))
  (println "loop started"))

(defn -main [& _]
  (let [message-ch (chan)
        stop-fn #(>!! message-ch :stop)]
    (start-capturing message-ch)
    (async/go
      (async/<! (timeout 30000))
      (stop-fn))
  ))
So this will so some stuff (I removed the uninteresting parts with ... ) every max 5 seconds. After 30 seconds. It should stop, but the process doesn't terminate. What am I missing?

Kurt Sys20:04:27

oh well, just adding (System/exit 0) works fine. Not sure if that's a proper way, though.

noisesmith20:04:22

if you use clojure's built in thread pools you should use shutdown-agents

noisesmith20:04:34

though calling System/exit explicitly is a workaround

noisesmith20:04:58

(nb. don't use shutdown-agents unless you are trying to make the vm exit, it will cause strange errors otherwise)

Kurt Sys20:04:12

ok, thx... What is preferred?

didibus22:04:37

I prefer a call to System/exit personally.

👍 4
noisesmith22:04:14

there are two different operations here - System/exit means you want the vm to exit, and don't care what other threads are doing right now

noisesmith22:04:18

shutdown-agents means that the vm can exit once all non-daemon threads (including futures / go blocks etc.) exit, but nobody is forcefully shut down

noisesmith22:04:00

if you don't use shutdown-agents, you'll still get an exit after 30 seconds, as seen above, it's just that that timeout passes before the threads are fully reclaimed so exit can happen

didibus22:04:52

Yes, that's true. shutdown-agents will not exit immediatly, it will wait for all tasks already sent to the threads to complete first, while System/exit will quit immediatly

didibus22:04:01

So it depends what you want

Jack Kiernan23:04:31

Hey I was curious curious if anyone could tell me the advantages to learning clojure over java? Trying to figure out what to learn. Do I need to learn Java first to learn Clojure? Does Clojure have disadvantages to Java? I know a little Python already (I know they're not related just providing context to my knowledge)

andy.fingerhut23:04:17

You do not need to learn Java in order to learn Clojure. It is fairly common when using Clojure for projects, that one will need to know how to read the API documentation for how to use one or more Java libraries that do useful things related to your project.

andy.fingerhut23:04:42

So at least having a basic working knowledge of what Java means by terms like class, interface, method, primitive types, are good things to know. You certainly don't need to know enough to design a Java class library, or develop a full Java application.

seancorfield23:04:49

I think an advantage of Clojure over Java is that Clojure is a much simpler language than Java (while still having access to all the Java libraries) and because it has immutable data structures, there are a lot of errors you can make in Java that can't happen in Clojure (especially if you start getting into concurrent code).

seancorfield23:04:59

A "disadvantage" of Clojure compared to Java is that it's more of a niche language so there are fewer jobs and less documentation/books/etc.

Phil Hunt10:04:29

On the other hand, there are some fantastic books and code examples for ancestors of Clojure, e.g. PAIP, SICP etc.

😲 4
noisesmith23:04:35

then there's the fact that "simpler" doesn't mean "easier" if you already know languages descended from Algol. Clojure's ancestors are more obscure so learning Clojure is harder if you already know how to program in a mainstream language. People will argue this is a benefit :D

noisesmith23:04:03

but it's definitely an upfront cost for people coming from mainstream languages