Fork me on GitHub
#beginners
<
2021-09-13
>
John Bradens01:09:44

Hi I'd just like to clarify something: so with shadow-cljs, I can run "watch app" and when I make changes to cljs files, I get to see the changes in the browser as soon as I save the files. This is awesome. When I make changes in clj files, I have to recompile every time though, right? Or is there a way I can see the changes immediately when I save those files as well? Basically I just want to make sure everything is still working each time I update a clj file. Or is this basically what writing tests is for?

Fredrik02:09:01

Are you using CIDER?

Fredrik02:09:02

If so (I'm sure other development environments also support this), you can rerun tests on every save with cider-auto-test-mode , and you can reload your entire namespace with cider-ns-reload . For an editor-independent (and apparently better) way to achieve the latter, you can use

clojure.tools.namespace.repl/refresh
from https://github.com/clojure/tools.namespace

drewverlee02:09:03

When you eval expressions, that is send them to the repl, it will know. But if your running an http server, you might have to do more to make sure it reloads. See this for details https://dev.to/kelvinmai/introduction-to-clojure-ring-2eff

Fredrik02:09:50

Namespace reloading is a very coarse-grained method (it tries to refresh the entire state of your running application). If you have any specific parts of your code you'd like to more easily update, maybe we could offer some better advice.

Fredrik02:09:02

Reloading code with shadow-cljs is a very smooth process because there is usually a clean separation between state+data and actions in the cljs code. Reloading usually only changes how actions work, preserving the state in your app.

John Bradens02:09:19

Thanks for the answers. I feel like I might be too ignorant to be able to articulate my question well. Basically right now, I use lein, so I do lein run and then shadow-cljs watch app to see my app in localhost. Every time I make a change in a file, I double check in localhost to see that it's working. So when I make cljs changes, I don't have to re-run any commands. But when I make clj changes, I re-do lein run. I feel like this might not be the best way of going about things... any resources you can point me to, or general things to look into? I do have cider on my emacs and I'll look into those cider commands more. I'll also look into the ring link that's posted.

Fredrik02:09:22

What pieces of your app are currently requiring you to restart the repl? Often you can just re-evaluate a function in the repl and it'll pick that up.

John Bradens02:09:08

I'm a total beginner so I'm basically experimenting with adding a bunch of random features and also trying to understand routing, websockets, MVC and all that. I edit all sorts of different pieces all the time since I'm trying to learn as much as I can. If I understand correctly, it sounds like I would run lein repl, then go into the namespace I'm currently editing, and then test the functions there to see that they work instead of reloading & compiling the whole app?

Fredrik02:09:28

Testing functions directly in the editor is probably how most people do it, at least when we're just messing around

John Bradens02:09:06

My problem was that I did lein run and ran shadow-cljs, then when I was editing things in clj files, those changes weren't reflected. And then when I finally recompiled I found out I broke a bunch of stuff without realizing.

John Bradens02:09:36

Ok thanks so much for that advice. I don't think any tutorials I did have gone over testing in the editor directly. Is that what I can use the cider commands for in emacs?

John Bradens02:09:45

Thanks for all the help btw @U024X3V2YN4

John Bradens02:09:58

I'll google this stuff too and will probably find some tutorials on it. Thanks again.

Fredrik02:09:47

You're only welcome 🙂 shout out if you have more questions

Fredrik02:09:24

CIDER can find and run tests for you, giving you access to the test-driven style if you want. But it is definately worth trying to get good at using the repl, modyfing, re-evaluating and testing functions (even just in (comment) blocks!) as you go

❤️ 2
John Bradens02:09:47

Cool, now I know the next thing to practice! 🙂

popeye06:09:35

I did not understand this statement of lazy-seq ,

clojure.core/lazy-seq accepts one or more forms that produce a sequence of nil (when the sequence is fully realized) and returns a seqable data structure that invokes the body the first time the value is needed and then caches the result.

Fredrik06:09:44

It's not only for recursion.`lazy-seq` can be used anywhere you'd benefit from a delay of computation. A common example is to imagine you want to process a large text file line-by-line. The size of the file might be several GBs, too big to hold in memory all at once. If you look at the source code of line-seq , you can see a tactic for processing such a file: Read a line, then recurse in a lazy-seq. After processing, each line could be garbage collected, thereby freeing up space in memory.

popeye06:09:43

what is the meaning of caching the result? in lazy-seq?

Fredrik06:09:35

The computation in the body of lazy-seq is only done the first time you try to access that value

popeye06:09:35

(defn positive-numbers 
	([] (positive-numbers 1))
	([n] (lazy-seq (cons n (positive-numbers (inc n))))))

popeye06:09:09

in this function it calls iterativly, what do you mean by only done the first time you try to access that valu

Fredrik06:09:06

(positive-numbers) returns a lazy sequence of numbers. If you call (take 5 ...) on it, only the first five values will be computed. If you then do (take 10 ...) on the same sequence, it will remember those first five values, but has to compute the next five. Notice that there actually are infinitely many values in the sequence, but it will only produce for you as many as you ask of it

popeye06:09:06

ahh I understood it! i was just remember the definition, but now I am able t connect the dot! thanks for your write! 🙂

Fredrik06:09:41

To demonstrate when execution happens, you could try

(def a (cons 1 (lazy-seq (cons 2 (lazy-seq (println "Done!") (list 3))))))
(nth a 0)
(nth a 1)
(nth a 2)
(nth a 2)
and see the output in the repl.

popeye06:09:58

one more question!, (take 5 (positive-numbers)) positive-number will call fist and then take right.?How cllojure handles it? just resulting 5 values.

Fredrik06:09:33

Yes, you are first producing the lazy sequnce, then taking the first five values from it. Are you asking how take works?

popeye06:09:22

i got take works! but did not get how clojure handles take, because positive-number will call first right?

Fredrik06:09:12

Yes, but remember that positive-number returns a lazy sequence. That means it doesn't necessarily contain all its values yet, just the recipe for how to compute them.

Fredrik07:09:01

If you are trying to look at a lazy sequence in the repl, it will probably try to take as many values from it as it can. This means that lazy sequences can potentially blow up your repl.

popeye07:09:28

Thanks, that helped

popeye06:09:37

I understood that lazy-seq is only used for calling safe recursion, to avoid stackoverflow, is my understanding right?

Maravedis07:09:00

Hello. Something that I need to do quite often is to transform a map with two elements, grouping by the first and wanting a sequence of the second. Example:

[{:a 1 :b 2} {:a 1 :b 3} {:a 2 :b 5}] => {1 [2 3] 2 [5]}
I usually use group-by then map on values using specter, but is there a more idiomatic way to do this, because it feels like my solution is a waste of space and computation time.

Fredrik07:09:50

A map doesn't have a notion of positionality

Fredrik07:09:26

There is no well-defined way to talk about a first and second element

Fredrik07:09:48

Can you group by the key instead?

Maravedis07:09:55

Ohg, yeah, soryr, I didnut use the correct termn.

Maravedis07:09:06

I meant "group by a key and have the value of the other".

Maravedis07:09:51

Today I do something like this:

(def data [{:a 1
              :b 2}
             {:a 1
              :b 3}
             {:a 2
              :b 5}])
  (->> data
       (group-by :a)
       (sp/transform [sp/MAP-VALS sp/ALL] :b))

Fredrik08:09:46

A pure Clojure version could look something like

(defn fun [m]
 (reduce
  (fn [result m]
    (update result (:a m) (fnil conj []) (:b m)))
  {} m))

🙌 2
Fredrik08:09:59

For what it's worth, it's 50% faster.

Maravedis08:09:18

Nice. Thanks.

Benjamin10:09:09

what is a simple way to load some user config? In elisp I can just load the file because there are no namespaces, how do I deal nicely with this in clojure?

Johan Thorén10:09:30

What's the format of the user config? I've done the following for reading a config file containing an edn map:

(defn- read-config
  ([k]
   (try
     (let [config (clojure.edn/read-string (slurp (config-file)))]
       (get config k))
     (catch java.io.FileNotFoundException _e nil)))
  ([]
   (try
     (clojure.edn/read-string (slurp (config-file)))
     (catch java.io.FileNotFoundException _e nil))))
This returns a map containing the whole configuration, or just a specific key. Assumes that (config-file) will return a string containing the path to the file.

Fredrik10:09:55

Any code placed in a user.clj on the classpath will be available under the user namespace. This is the quick and dirty way.

👍 2
Benjamin10:09:44

Comming from elisp it seems to be natural to give the user the power to also redefine functions etc. There is not really a use case in my app for this atm though.

Fredrik10:09:54

For more control, you can do like @U02DW53HCSE above, or use a library like https://github.com/juxt/aero

👍 2
Jakub Zika12:09:06

Hello everybody, would you recommend some library / article about how to create a HTTP proxy in Clojure? I use Ring+jetty

popeye13:09:30

I would like to explore the clojure project which using compojure mount integrant, has anybody make use of this DI in there open source project? Wanted to learn how this is written

tolitius22:09:47

@U01J3DB39R6 for compojure / mount, take a look at: https://github.com/tolitius/hubble

👀 2
🙌 2
Chase14:09:16

The readme also includes a link to a sister project using Integrant and Reitit (vs Component and Compojure)

Benjamin15:09:50

does somebody know why Var$Unbound cannot be cast to IAtom with this setup?

delaguardo15:09:11

because clojure require some ceremony before you can call the function provided by your namespace. https://clojure.org/reference/java_interop#_calling_clojure_from_java

delaguardo15:09:36

IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("com.github.benjaminasdf.idlelib.core"));

IFn f = Clojure.var("com.github.benjaminasdf.idlelib.core", "-initProj");
f.invoke(...)
something like that

delaguardo15:09:37

tldr; when calling that function from java - com.github.benjaminasdf.idlelib.core$_initProj`.invokeStatic(project)` you missing namespace “loading” which will initialize the var -project

👀 2
delaguardo15:09:00

tldr2; calling clojure from java is not a smooth sailing as it might be seeing at first (

Benjamin15:09:49

thanks a lot! This loading namespace is interesting do you know where to read on this?

noisesmith20:09:50

the same limitation applies in clojure - even if the ns was AOT compiled you shouldn't try to use its functions without loading the namespace first (usually via require)

popeye15:09:17

i found below code in one of the repo , what is the meaning of #?(:cljs and #?(:clj

#?(:cljs (:require
             [goog.string :as gstring]
             [goog.string.format]))
  #?(:clj (:import
            [clojure.lang IDeref])))

Fredrik15:09:32

They are called reader conditionals. You can read about them here: https://clojure.org/guides/reader_conditionals

🙌 2
Fredrik15:09:10

In short they allow the same source files to be read both by Clojure and ClojureScript, but with conditional evaluation depending on which platform you're on.

Fredrik15:09:32

They are commonly used in library code so that the same jar can be used on both platforms.

popeye15:09:42

So when this namespace is called from either clj or cljs depends on that package will bw imported right?

Fredrik15:09:50

Yes, exactly. On Clojure, it will import clojure.lang.IDeref , on ClojureScript it will import the goog stuff, which is particular to ClojureScript.

🙌 2
Franco Gasperino15:09:46

Good morning. What is the recommend route for evaluating the result of a clojure.spec.gen.alpha/check result summary set with the test framework (runners, assertions, and family) of clojure.test?

Franco Gasperino15:09:14

I'm happy to extract summary result key-val pairs from the former and apply them in equality tests in the latter. Is there a better practice?

Ryan20:09:28

I'm a bit lost on trying to transform a table with some local state

Ryan20:09:54

I have x, I need to transform it into x-processed:

(def x
  [{:p 49 :v "red"}
   {:p 49 :v "blue"}
   {:p 1  :v "green"}
   {:p 1  :v "yellow"}]
  )


(def x-processed
  [{:p 49 :start 0   :v   "red"}
   {:p 49 :start 49  :v  "blue"}
   {:p 1  :start 99  :v  "green"}
   {:p 1  :start 100 :v "yellow"}]
  )

Ryan20:09:18

so just conj-ing. :start with the running sum of 😛

Russell Mull20:09:09

Have you looked at the reduce function?

Ryan20:09:04

Yes, its a bit confusing how I'd write the reduction function

Ryan20:09:19

so I looked at loop/recur.. but that seems like a worse way to do it

noisesmith20:09:51

the accumulator will hold your new vector so far

Ryan20:09:11

Ok so that's the working new conj'd map?

noisesmith20:09:13

from the last entry in the vector, the :start key will tell you where it left off - does that make sense?

Ryan20:09:32

I think so

noisesmith20:09:36

well, reduce has to take the whole vector of maps from call to call

noisesmith20:09:59

so you'd get the :start out of the last map in the vector, then return a new vector with a new map on the end

Ryan20:09:42

ok, on the reduce page I see some signatures for the reduction fn that are like

(fn [accumulator current-item]  ...)

Ryan20:09:26

I think where Im getting caught up is the first call

emccue20:09:40

lets start with the loop/recur solution

noisesmith20:09:10

the first call gets the initial accumulator, plus the first item of the input

emccue20:09:20

(loop [running-total 0
       unprocessed   x
       processed     []]
  ...)

emccue20:09:09

this is roughly what you need to keep track of - your running total, the things that are left, and the things you have done

emccue20:09:32

(loop [running-total 0
       unprocessed   x
       processed     []]
  (if (empty? unprocessed)
    processed
    ...))

Ryan20:09:42

ok right

emccue20:09:44

if you have nothing left to process you are done

emccue20:09:26

(loop [running-total 0
       unprocessed   x
       processed     []]
  (if (empty? unprocessed)
    processed
    (recur (+ running-total (:p (first unprocessed)))
           ...))

emccue20:09:41

you want to set the new running total each time through

noisesmith20:09:51

unprocessed is a collection not a single item

Ryan20:09:59

right loop's bindings are like let, and recur like re-maps bindings for the next pass?

noisesmith20:09:02

which is why this is messy in loop / recur

emccue20:09:51

the best solution i could think of would be to reduce over your collection to get a sequence of the running totals

Ryan20:09:20

maybe my approach is the inelegant part

emccue20:09:23

({:p 49 ...} {:p 1 ...} ...) -> (0 49 50 ...)

emccue20:09:37

and then "zip" that with the original collection

emccue20:09:03

([{:p 49 ...} 0] [{:p 1 ...} 49] ....)

Ryan20:09:07

because at the end of the day, I want to generate a random number that produces red 49%, blue 49%, green 1% yellow 1%

Russell Mull20:09:07

> always be empowered to fall back on this if you cant find a "beautiful" solution

Russell Mull20:09:11

That is very good advice

emccue20:09:26

then map over that list of tuples to make your final maps

Ryan20:09:40

that makes sense

noisesmith20:09:12

(reduce (fn [acc item]
          (let [prev (peek acc)
                {:keys [start p] prev}]
            (conj acc (assoc item :start (+ p start)))))
        []
        x)

noisesmith20:09:28

the task itself already builds the running total

noisesmith20:09:07

all the info you need to make the next item is contained in the prev item and next input

Ryan20:09:43

Yeah I noticed its similarities to a basic sum

Ryan20:09:00

except I want to conj in the running total instead of reducing to a single sum

noisesmith20:09:42

fixed

(def x
  [{:p 49 :v "red"}
   {:p 49 :v "blue"}
   {:p 1  :v "green"}
   {:p 1  :v "yellow"}])

(reduce (fn [acc item]
          (let [prev (peek acc)
                {:keys [start p] :or {p 0 start 0}} prev]
            (conj acc (assoc item :start (+ p start)))))
        []
        x)
[{:p 49, :v "red", :start 0}
 {:p 49, :v "blue", :start 49}
 {:p 1, :v "green", :start 98}
 {:p 1, :v "yellow", :start 99}]

noisesmith20:09:27

@U020G0VEL75 also, regarding your final goal, I made a similar weighted choice script recently http://ix.io/3vcW

noisesmith20:09:48

it lets me rank characters for a game, then picks one based on that weighted chance

Fredrik20:09:55

(map #(assoc %1 :start %2) x (reductions + (map :p x)))

💯 2
Ryan20:09:33

This is actually to generate some Savage Rifts NPCs 😄

noisesmith20:09:28

@U024X3V2YN4 but will that start with a 0?

noisesmith20:09:50

I think you might need (cons 0 (map ...)) there

Fredrik20:09:14

Yes, or rather (map #(assoc %1 :start %2) x (reductions + (cons 1 (map :p x)))) . EDIT: I take this back. I was worried about off-by-one errors. You should just make sure that the random picker function has the right probability distribution.

noisesmith20:09:39

(haha math brain, I see 1 as a placeholder and assume multiplication was specified somewhere)

Ryan20:09:15

Ah the way I'm calculating the results its just to sum them, generate a random int in the range and figure out where it lies, that way I didn't have to worry about summing to 100 if I combine some tables

Ryan20:09:16

none of this is precision, just fun and games and it seems like doing it this way touched some parts of clojure I've been blissfully ignorant of avoiding state and trying to do things functionally 🙂. Thanks for all your help, much to think about!

noisesmith20:09:27

@U020G0VEL75 if you look at the code I linked, I basically collapse the vector of hash-maps with :p and :v entries into a hash map mapping each :v to a :p (for brevity if nothing else), and do the same sum operation you do, but I never need a running total - instead I walk the entries subtracting the p for each one, and stopping when I reach a number <= zero

noisesmith20:09:29

a running total would let me do a binary search, but the startup time of the jvm (not to mention clojure startup time) is orders of magnitude greater than the time I'd save by not linearly searching a list that short

Ryan21:09:24

oh sure, my deployment target is a clojurescript webapp, so i'm almost never doing a cold startup to run again, the tool suite is running waiting for me to hit 'generate npc' again 🙂

Ryan21:09:47

I love cljs, but boy can debugging it be painful

Ryan21:09:42

would running it cmdline lend itself to that.. bubushka or whatever the tool for using clj for shell scripting is called?

noisesmith21:09:50

babashka lets you do a subset of what jvm clojure can do, with a much shorter startup

noisesmith21:09:33

it can be helpful to put the "interesting" part of your code in cljc files, so that you can test / repl debug with jvm clojure

noisesmith21:09:19

both clj and cljs will use cljc if it finds it, so the setup for that isn't hard

noisesmith21:09:47

or you can go deep into the weird world of js, and use the clojurescript tooling that integrates with that world (but I find that annoying because js moves very fast while improving slowly, and the tooling is a bunch of annoying rube goldberg monstrosities)

Ryan21:09:47

I mean I am using shadow-cljs 🙂

Ryan21:09:12

I'm also coming from doing typescript / angular dev work mostly so its a runtime Im comfortable with, tho interop is weird, very weird

noisesmith21:09:40

right, but if debugging is still a pain there's yet more tooling you can hook up to fix that - it's just tedeous half-broken stuff in my experience

Ryan21:09:07

I think most of my debugging hell comes into the last mile intersection with react

Ryan21:09:06

but I'm going to see how much of taht I can do in cljc side because that does sound better

noisesmith21:09:45

I've also had some luck with making cljs pages that run clojure.test tests and write output via document.write to an unstyled page

Ryan21:09:18

That sounds fun

noisesmith21:09:46

it's pretty easy - use with-out-str to capture the test output, do a page injection

noisesmith21:09:23

the hard part is keeping it simple so you can get to the actual task at hand :D

Ryan21:09:00

but the yak needs to get shaved eventually!

Rob Haisfield20:09:25

Hey, long time no see! How do I set up my project.clj and ns require function to use a Java library? The actual java interop of writing functions isn’t the hard part, just not seeing much on setting up a project with Leiningen for it

👀 2
noisesmith20:09:27

in the ns form you can use import to make shorthands for classes, but that's not neccessary in the project.clj you provide coordinates for the artifact containing the java class you need

noisesmith20:09:02

lein help sample has extensive examples in the form of an example project.clj

noisesmith20:09:40

look for the lib you need in maven, then translate group foo, artifact bar, version 1.2 to [foo/bar "1.2"]

noisesmith20:09:50

that particular project maintainer expects you to build and install to your local maven cache instead https://github.com/Philipinho/CoinGecko-Java/issues/15 which is annoying

noisesmith20:09:31

tl;dr this would be easy, except the people behind that project are being weird

emccue21:09:35

maybe fork their lib and add it on jitpack?

emccue21:09:40

idk thats some wierdness

emccue21:09:08

to be fair its not much code - maybe just translating the http calls you need would be better?

noisesmith21:09:47

oh yeah, when the tooling side gets weird it's often easier to just not use the lib

emccue21:09:21

@GET("ping")
    Call<Ping> ping();

    @GET("simple/price")
    Call<Map<String,Map<String, Double>>> getPrice(@Query("ids") String ids,
                                      @Query("vs_currencies") String vsCurrencies,
                                      @Query("include_market_cap") boolean includeMarketCap,
                                      @Query("include_24hr_vol") boolean include24hrVol,
                                      @Query("include_24hr_change") boolean include24hrChange,
                                      @Query("include_last_updated_at") boolean includeLastUpdatedAt);

    @GET("simple/token_price/{id}")
    Call<Map<String,Map<String, Double>>> getTokenPrice(@Path("id") String id, @Query("contract_addresses") String contractAddress,
                               @Query("vs_currencies") String vsCurrencies, @Query("include_market_cap") boolean includeMarketCap,
                               @Query("include_24hr_vol") boolean include24hrVol, @Query("include_24hr_change") boolean include24hrChange,
                               @Query("include_last_updated_at") boolean includeLastUpdatedAt);

    @GET("simple/supported_vs_currencies")
    Call<List<String>> getSupportedVsCurrencies();

    @GET("coins/list")
    Call<List<CoinList>> getCoinList();

    @GET("coins/markets")
    Call<List<CoinMarkets>> getCoinMarkets(@Query("vs_currency") String vsCurrency, @Query("ids") String ids,
                                           @Query("order") String order, @Query("per_page") Integer perPage,
                                           @Query("page") Integer page, @Query("sparkline") boolean sparkline,
                                           @Query("price_change_percentage") String priceChangePercentage);

    @GET("coins/{id}")
    Call<CoinFullData> getCoinById(@Path("id") String id, @Query("localization") boolean localization, @Query("tickers") boolean tickers,
                               @Query("market_data") boolean marketData, @Query("community_data") boolean communityData,
                               @Query("developer_data") boolean developerData, @Query("sparkline") boolean sparkline);

    @GET("coins/{id}/tickers")
    Call<CoinTickerById> getCoinTickerById(@Path("id") String id, @Query("exchange_ids") String exchangeIds,
                                           @Query("page") Integer page,@Query("order") String order);

    @GET("coins/{id}/history")
    Call<CoinHistoryById> getCoinHistoryById(@Path("id") String id, @Query("date") String date,
                                             @Query("localization") boolean localization);

    @GET("coins/{id}/market_chart")
    Call<MarketChart> getCoinMarketChartById(@Path("id") String id, @Query("vs_currency") String vsCurrency,
                                        @Query("days") Integer days);

    @GET("coins/{id}/market_chart/range")
    Call<MarketChart> getCoinMarketChartRangeById(@Path("id") String id, @Query("vs_currency") String vsCurrency,
                                                  @Query("from") String from, @Query("to") String to);

    @GET("coins/{id}/status_updates")
    Call<StatusUpdates> getCoinStatusUpdateById(@Path("id") String id, @Query("per_page") Integer perPage, @Query("page") Integer page);

    @GET("coins/{id}/contract/{contract_address}")
    Call<CoinFullData> getCoinInfoByContractAddress(@Path("id") String id, @Path("contract_address") String contractAddress);

    @GET("exchanges")
    Call<List<Exchanges>> getExchanges();

    @GET("exchanges/list")
    Call<List<ExchangesList>> getExchangesList();

    @GET("exchanges/{id}")
    Call<ExchangeById> getExchangesById(@Path("id") String id);

    @GET("exchanges/{id}/tickers")
    Call<ExchangesTickersById> getExchangesTickersById(@Path("id") String id, @Query("coin_ids") String coinIds,
                                                       @Query("page") Integer page, @Query("order") String order);

    @GET("exchanges/{id}/status_updates")
    Call<StatusUpdates> getExchangesStatusUpdatesById(@Path("id") String id, @Query("per_page")Integer perPage,
                                                               @Query("page") Integer page);

    @GET("exchanges/{id}/volume_chart")
    Call<List<List<String>>> getExchangesVolumeChart(@Path("id") String id,@Query("days") Integer days);

    @GET("status_updates")
    Call<StatusUpdates> getStatusUpdates();

    @GET("status_updates")
    Call<StatusUpdates> getStatusUpdates(@Query("category") String category, @Query("project_type") String projectType,
                                  @Query("per_page") Integer perPage, @Query("page") Integer page);

    @GET("events")
    Call<Events> getEvents();

    @GET("events")
    Call<Events> getEvents(@Query("country_code") String countryCode, @Query("type") String type,
                           @Query("page") Integer page, @Query("upcoming_events_only") boolean upcomingEventsOnly,
                           @Query("from_date") String fromDate, @Query("to_date") String toDate);

    @GET("events/countries")
    Call<EventCountries> getEventsCountries();

    @GET("events/types")
    Call<EventTypes> getEventsTypes();

    @GET("exchange_rates")
    Call<ExchangeRates> getExchangeRates();

    @GET("global")
    Call<Global> getGlobal();

emccue21:09:41

just these mechanical calls and java type definitions

Rob Haisfield04:09:29

@U3JH98J4R so what do I do with that code? I’m very much a beginner and these “project details” of java interop are really throwing me.

emccue04:09:21

yeah sorry that might not have been super helpful

emccue04:09:15

clojure libraries like https://github.com/gnarroway/hato take in a data structure describing how to make and interpret an http request

emccue04:09:46

so for example {:method :get :url ".../ping" :as :json} would describe the ping callyou want to make

emccue04:09:29

so you can make a function like

(defn ping []
  {:method :get
   :url    ".../ping"
   :as     :json})

emccue04:09:34

and then pass the resulting structure to something like (hc/request (ping))

emccue04:09:51

the big code block above shows a bunch of java annotations that describe how to make the requests

emccue04:09:29

which you could reasonably translate to one of those data structure formats

emccue04:09:11

so like getCoinMarketChartRangeById

emccue04:09:10

(defn coin-market-chart-range-by-id [{:keys [id vs-currency from to]}]
  {:method       :get
   :url          (str base-path "coins/" id "market_chart/range")
   :query-params {"vs_currency" vs-currency
                  "from"        from
                  "to"          to}
   :as           :json})

emccue04:09:58

and then

(require '[hato.client :as hc])

(hc/request (coin-market-chart-range-by-id { ... }) 

emccue04:09:01

which is roughly what I would recommend, since the java library is simple enough to recreate (you don't have to pre-validate their api responses and its a fairly short list of things to write) and they are wierdly obtuse about how they are distributing their code

Rob Haisfield05:09:25

Ah okay I understand a little better… Seems like there’s a lot of locations for me to mess stuff up there. I’m sorry, I really appreciate you writing all this out but I’m not sure how confident I feel about creating a new library from HTTP. How would I do it through the jitpack method?

Rob Haisfield05:09:35

For some reason, this isn’t working

Rob Haisfield05:09:57

(ns rob-dsl.play
  (:require [com.rpl.specter :refer :all]
            [com.github.classicrob.CoinGecko-Java :as coin]))

Execution error (FileNotFoundException) at rob-dsl.play/eval4496$loading (play.clj:1).
Could not locate com/github/classicrob/CoinGecko_Java__init.class, com/github/classicrob/CoinGecko_Java.clj or com/github/classicrob/CoinGecko_Java.cljc on classpath. Please check that namespaces with dashes use underscores in the Clojure file name.

emccue06:09:15

for the jitpack method you need to mae sure to add jitpack as a repository in your project.clj

emccue06:09:33

and to import a class you don't use require

emccue06:09:38

you use :import

emccue06:09:42

(ns your.ns
  (:require [some.dep :as d])
  (:import (com.litesoftwares.coingecko CoinGeckoApi))

Rob Haisfield06:09:02

Ohhh okay I’ll try the :import in the morning. I’m about to go to bed but I’ll let you know if it works. I’ve got jitpack in my project.clj in the way the site said

Rob Haisfield15:09:37

It worked, thank you so much! I’m going to save this whole thread for when I feel more comfortable implementing a new http based lib

Rob Haisfield20:09:38

If it’s helpful, I’m trying to use this Java library https://github.com/Philipinho/CoinGecko-Java