beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
seancorfield 2021-01-11T00:20:44.381100Z

Updated it. Hopefully it's clearer now @g3o next time you update from the usermanager repo or read the code?

yiorgos 2021-01-14T19:51:36.060200Z

Sweet, thank you very much!

2021-01-11T00:32:59.383600Z

i'd like to make sure that clojure code i evaluate locally is instrumented by default (ideally without modifying the source code i'm evaluating). putting

:instrument                     {:injections [(do (require  '[clojure.spec.test.alpha :as stest]
                                                             '[clojure.spec.alpha :as s])
                                                   (s/check-asserts true)
                                                   (stest/instrument))]}
in my .lein/profiles.clj seems to do the trick. is there a simpler way to accomplish this? i'm vaguely concerned that this adds a lot of overhead to on the fly form evaluation in emacs

2021-01-11T00:49:36.384100Z

if i were smart about emacs i'd figure out how to run that as a part of a modified jack in command, perhaps

seancorfield 2021-01-11T00:50:36.384800Z

@michael740 Be aware that (stest/instrument) will only instrument functions that are already defined and have a spec.

seancorfield 2021-01-11T00:51:34.385700Z

It won't instrument any code that gets loaded afterward -- you'd need to call (stest/instrument) again after defining those new functions.

seancorfield 2021-01-11T00:55:31.385900Z

user=> (require '[clojure.spec.test.alpha :as st])
nil
user=> (st/instrument)
[]
user=> (require '[clojure.spec.alpha :as s])
nil
user=> (defn foo [x] (* x x))
#'user/foo
user=> (s/fdef foo :args (s/cat :x int?))
user/foo
user=> (foo "a")
Execution error (ClassCastException) at user/foo (REPL:1).
class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')
user=> (st/instrument)
[user/foo]
user=> (foo "a")
Execution error - invalid arguments to user/foo at (REPL:1).
"a" - failed: int? at: [:x]
user=>

2021-01-11T01:09:02.387Z

thanks @seancorfield - that's a great point, and something i'm wrestling with as we speak.

2021-01-11T01:10:38.388500Z

it doesn't automagically keep my repl state in good condition as i work, but at least it loads up existing specs in the beginning. i've been working on code for the last few days that i had specced out in 2020 - i'd completely forgotten to instrument it during local development. 🀦

seancorfield 2021-01-11T01:12:21.389800Z

What I tend to do is have my test namespace instrument my source namespace (specifically), so whenever I run tests -- which I do fairly often via a hot key while developing -- my source functions have instrumentation enabled.

seancorfield 2021-01-11T01:12:43.390400Z

That way, as I add new functions and Specs, whenever I run tests, they get instrumented.

2021-01-11T01:27:26.392100Z

come to think of it - i was originally concerned that using leiningen's :injections would be overkill since it prepends the injections to all forms. but perhaps not, given what we're saying about how a single call to stest/instrument can become stale

2021-01-11T02:20:36.395700Z

Hi, is there any clojure idiom to separate database code from general data/functions code? I'm an experienced software engineer with a background in Java/Go/Rust/Python and I'm having trouble "organizing" the persistence layer. I've looked at some established projects such as Metabase, and it seems the db code is scattered "all over the place". Is that considered a good practice? Thank you!

2021-01-11T03:10:20.395800Z

Normally you wrap the DB calls in functions that only create, update, remove or delete from the DB and take a db-client as an argument.

2021-01-11T03:10:57.396Z

Then you try and use those functions as much as you can exclusively from the top-level API functions.

2021-01-11T03:12:46.396200Z

But in practice, you'll often break that rule, at least a bit, so you could end up "using those functions directly". The question then is where do you get the DB client instance from? The best practice for this would be to carry it down to the place that needs it, injecting it at every function call along the way. People often do that where they pass down a context map of some sort, and the db-client is a key inside it.

2021-01-11T03:13:48.396400Z

Since you still should try to keep it relatively flat, their shouldn't be that many places where you need to "pass it down" more than 2 levels, so it can be quite manageable.

2021-01-11T03:17:18.396600Z

Another alternative is very similar to OOP langs. Their functions that do CRUD directly access the db-client state. Normally that's managed with some "component-like" library. At its simplest, imagine a namespace with a (def db-client (DbClient.)) and the CRUD functions in the namespace just get the client from that directly.

2021-01-11T03:18:05.396800Z

In practice, a component library is used, which will create a sort of "instance" for the namespace and inject the client into it for the functions to use.

jacklombard 2021-01-11T03:24:49.398500Z

Though this is a datomic question, I thought it belongs here as I think I'm missing something trivial

(comment
  (def db-uri "datomic:<mem://localhost:4334/indium%22|mem://localhost:4334/indium">)
  (def db-uri "datomic:<dev://localhost:4334/indium%22|dev://localhost:4334/indium">)

  (d/create-database db-uri)

  (def conn (d/connect db-uri))

  (def db (d/db conn))

  (def movie-schema [{:db/ident       :movie/title
                      :db/valueType   :db.type/string
                      :db/cardinality :db.cardinality/one
                      :db/doc         "The title of the movie"}

                     {:db/ident       :movie/genre
                      :db/valueType   :db.type/string
                      :db/cardinality :db.cardinality/one
                      :db/doc         "The genre of the movie"}

                     {:db/ident       :movie/release-year
                      :db/valueType   :db.type/long
                      :db/cardinality :db.cardinality/one
                      :db/doc         "The year the movie was released in theaters"}])

  (def first-movies [{:movie/title        "Explorers"
                      :movie/genre        "adventure/comedy/family"
                      :movie/release-year 1985}
                     {:movie/title        "Demolition Man"
                      :movie/genre        "action/sci-fi/thriller"
                      :movie/release-year 1993}
                     {:movie/title        "Johnny Mnemonic"
                      :movie/genre        "cyber-punk/action"
                      :movie/release-year 1995}
                     {:movie/title        "Toy Story"
                      :movie/genre        "animation/adventure"
                      :movie/release-year 1995}])

  (d/transact conn movie-schema)

  (d/transact conn first-movies)

  (def all-movies-q '[:find ?e
                      :where [?m :movie/title ?e]])

  (d/q all-movies-q db))
I get the following error when I run (d/q all-movies-q db)
Execution error (Exceptions$IllegalArgumentExceptionInfo) at datomic.error/arg (error.clj:79).
:db.error/not-an-entity Unable to resolve entity: :movie/title

jacklombard 2021-01-11T03:25:40.399600Z

I've tried both the dev and mem protocols seperately and both return the same error. I have a dev transactor running when I was using the dev protocol.

2021-01-11T03:29:42.401400Z

there is a #datomic channel you can try asking in, but my guess would be transact is executing asynchronously, so the execution of your first and second transactions are overlapping, but I don't really know, you would need to check the documentation to confirm that and see how you can wait on the result of a transaction

jacklombard 2021-01-11T03:31:05.401600Z

I can deref the transaction. I'll try this and also ask in the datomic channel thanks!

2021-01-11T03:34:21.401800Z

Interesting... I've seen those approaches before, wasn't really sure if they were good practices or not. Thank you!

2021-01-11T03:46:04.402Z

I looked a bit at metabase. It seems they do the latter. They use their own pseudo-ORM called toucan. When their app launches, they set the default database driver for toucan to use based on an environment configuration. So that's how you specify if you want to use RedShift, MySQL, Postgress, etc. From this point on, toucan can be used like a "singleton component". You pass it a query spec, which is not SQL, but some vector representation of some SQL query (they use honeysql for it). Toucan and or HoneySQL (not sure which one does the hard work), will then generate the SQL for the default database that was configured when the app started.

2021-01-11T03:47:07.402200Z

But Toucan add some ORM features too. So you can tell it to insert into a Model and it'll generate the SQL and do the insert for you

2021-01-11T03:50:25.402500Z

thanks for asking this question, @diego559. i also come from java/python and what little i've seen in clojure crud apps also suggests that it's not idiomatic to attempt any kind of separation between persistence layer interfacing and business logic

2021-01-11T03:51:55.402700Z

it was so striking that it really did make me curious whether there was some broad OO based rejection of sequestering the persistence layer in the clojure community

2021-01-11T03:53:45.402900Z

You have to ask yourself, why were you creating a DAL (data access layer) in Java?

2021-01-11T03:54:19.403200Z

ostensibly it was to hedge against future persistence-layer swap outs.

2021-01-11T03:55:51.403400Z

and to separate concerns - business logic from business-agnostic third party interfacing

seancorfield 2021-01-11T03:56:11.403600Z

You can swap DBs fairly easily if you're only using CRUD functions with next.jdbc (or any of the JDBC-based wrappers) but there definitely are differences between DBs that make anything more complex less portable.

2021-01-11T03:57:01.403800Z

In that sense, I do think the same is done in Clojure. In that the CRUD operations are often isolated in their own functions. In the case of Metabase, they're using their own ORM. So when you do (db/insert! User :name foo) toucan will handle whichever concrete DB you set it up to use, so no changes to your code necessary

seancorfield 2021-01-11T03:57:43.404Z

In reality, projects almost never switch persistence layers so ORM and much of that sort of stuff is a giant waste.

πŸ‘ 2
2021-01-11T03:57:47.404200Z

So metabase can "look" like it is not isolating the CRUD operations in functions, but it is, inside "db/insert!" itself.

2021-01-11T03:58:52.404500Z

@seancorfield - i've wondered that myself. i've also wondered - if you did switch persistence layers, wouldn't that be so traumatic as to require major surgery anyway? idk. i'm not that experienced overall to have a sense of it.

2021-01-11T03:59:40.404700Z

I think the other aspect for a DAL in Java, is that you want to normalize the data you get back and send to the DB, so that you get one of your model Classes, and send one of your model Classes. In Clojure, that's just not a problem, because you send data and get data back, and work with it directly. (well toucan actually tries to mimic a more Java OO style here, by defining models and abstracting the DB into it).

1
2021-01-11T04:00:55.405Z

that makes a lot of sense!

2021-01-11T04:02:09.405200Z

Most likely you wouldn't just switch DB, you'd be doing a re-architecture of your data model and storage which maybe requires a different DB. Like going from relational to NoSql or something like that. And ya, that be traumatic anyways, and require a lot to change, a DAL would not protect you from any of that work.

2021-01-11T04:03:24.405400Z

And if you change DB because like your contract expired, say you had Oracle DB and want to go free with say MariaDB. Even without a DAL, that's a much more minor break, since they both still use SQL, and they both use a JDBC driver to interact with them.

2021-01-11T04:07:10.405600Z

at my last workplace, we specifically guarded against changing db types, too. we fully expected to switch from, eg, relational to nosql. so we cut up all our data base calls into separate network calls - no joins! we did all the joining in the application layer, in the business logic layer

seancorfield 2021-01-11T04:07:50.405800Z

@michael740 There's a small subset of SQL that's completely portable and a larger subset that is mostly portable between the "big" DBs. Take a look at the tests in the next.jdbc for plenty of examples of how different DBs can be πŸ™‚

2021-01-11T04:07:51.406Z

it was a tremendous amount of boilerplate and network calls, i could hardly believe it. but it was taken quite seriously by the staff engineers.

2021-01-11T04:08:43.406200Z

wow!

2021-01-11T04:09:57.406400Z

That sounds like a typical Java monstrosity, suffering from the crux of over-engineering and speculative generality πŸ˜›

2021-01-11T04:11:00.406600Z

As an aside, I was able to inspect the Metabase codebase in 10 minutes, and get a pretty good grasp of how it works. Keep in mind I have no experience with Toucan or Metabase. And I've actually never done anything with Ring or Compojure (even though I know a little about them from reading up on them).

2021-01-11T04:11:04.406800Z

@didibus - rejecting "speculative generality" - is that clojurian?

2021-01-11T04:12:52.407Z

Good question, I would say so, though I don't know if I've heard it referred to as speculative generality in Clojure-world that much. I think its less rejecting it, and more encouraging things that are its opposite

2021-01-11T04:15:03.407200Z

Clojure tend to encourage small simple direct solutions to problem, but done in a way that they can accrete features over time without causing a bunch of things to break in the process, and making a mess of the code base.

seancorfield 2021-01-11T04:16:03.407400Z

We did migrate a segment of our data from MySQL to MongoDB and back again. Even with the shift from RDBMS to document-based storage, we were able to keep a lot of the very basic CRUD functions the same (in fact, within our one API, we supported both DBs dynamically and used the table name/collection name in the call as the discriminator).

πŸ™Œ 1
seancorfield 2021-01-11T04:17:08.407700Z

Everything beyond that very basic CRUD stuff had to be completely rewritten each time. But the simplicity of Clojure code made it relatively tractable. It would have been a nightmare in Java...

2021-01-11T04:17:41.407900Z

Just the fact that on average, more of your business logic will be pure, automatically means less to refactor.

2021-01-11T04:19:58.408100Z

yeah... i think that makes a lot of sense. you don't need as much structure because everything is lighter-weight anyhow

2021-01-11T04:21:03.408300Z

it's like going ultralight backpacking - you switch from heavy hiking boots with ankle support to trail runners because a) you're saving weight and b) you've already saved so much weight with the other gear that the support just doesn't mean as much at that point

seancorfield 2021-01-11T04:22:42.408500Z

@diego559 Going back to your original Q: the driving idea is to organize your code as much as possible where your business logic is as pure as can be, so you should strive for code that does all of the DB reading, then runs all the pure business logic, then performs all the DB updates.

βž• 1
seancorfield 2021-01-11T04:23:18.408700Z

In practice, your code is likely to be less structured but try to aim for that. It makes for more testable code and more maintainable code.

2021-01-11T04:24:10.408900Z

You can take a Clojure app pretty far with just:

(def db ...) ; some jdbc data source config

(defn some-api [...]
  (-&gt; (get-the-thing db)
    (transform-it)
    (put-the-thing-back) ; or return the transformed thing))

seancorfield 2021-01-11T04:24:19.409100Z

But remember that "it's just data" so what you store in the DB and what you read back from the DB is typically just hash maps (and sequences of them). That means that reading/writing is a near 1:1 mapping.

2021-01-11T04:42:52.409400Z

Sounds good... lots of good info here πŸ™‚

2021-01-11T04:43:32.409600Z

Other than Metabase, any other prominent Clojure open-source project to learn from?

2021-01-11T04:43:47.409800Z

Any recommendations?

2021-01-11T04:44:22.410Z

(I'm interested in apps - e.g. services, apis, etc, not libraries)

2021-01-11T04:50:07.410200Z

I'd check out https://github.com/cljdoc/cljdoc as another good large example

2021-01-11T04:51:04.410700Z

Here's another big one: https://github.com/riemann/riemann

seancorfield 2021-01-11T04:52:33.411100Z

@diego559 Overall there aren't many Clojure apps available as open source (unfortunately) so there's very little to learn from.

2021-01-11T04:52:43.411300Z

https://github.com/clojars/clojars-web

2021-01-11T04:58:12.411700Z

Thanks!

2021-01-11T05:09:40.411900Z

This one looks interesting as well: https://github.com/chr15m/slingcode the project too, just found it, pretty cool

jumar 2021-01-11T07:38:23.414200Z

I'm trying to install Clojure on Windows (have been using Clojure on MacOS for years) following https://github.com/clojure/tools.deps.alpha/wiki/clj-on-Windows Invoke-Expression (New-Object <http://System.Net|System.Net>.WebClient).DownloadString('<https://download.clojure.org/install/win-install-1.10.1.763.ps1>') The installation finished OK, I restarted Powershell and tried clj:

clj : The 'clj' command was found in the module 'ClojureTools', but the module could not be loaded. For more
information, run 'Import-Module ClojureTools'.
At line:1 char:1
+ clj
+ ~~~
    + CategoryInfo          : ObjectNotFound: (clj:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CouldNotAutoloadMatchingModule
I'm using Parallels Desktop VM running on MacOS

jumar 2021-01-11T07:43:25.414500Z

Ok, running the suggested command helped

Import-Module ClojureTools
=&gt; 
Import-Module : File C:\Users\jumar\Documents\WindowsPowerShell\Modules\ClojureTools\ClojureTools.psm1 cannot beloaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170. At line:1 char:1 + Import-Module ClojureTools + ~~~~~~~~~~~~~~~~~~~~~~~~~~     + CategoryInfo          : SecurityError: (:) [Import-Module], PSSecurityException     + FullyQualifiedErrorId : UnauthorizedAccess,Microsoft.PowerShell.Commands.ImportModuleCommand
   
# run powershell as admin and then:
Set-ExecutionPolicy Unrestricted

Piotr BrzeziΕ„ski 2021-01-11T08:24:57.417500Z

Is it usual to get a bit overwhelmed by the peg game development while going through braveclojure? πŸ™‚ I feel like it was a leap of faith compared to previous chapters.

βž• 1
Christian 2021-01-11T09:04:17.418Z

Are there other resources than "https://dragan.rocks/articles/19/Deep-Learning-in-Clojure-From-Scratch-to-GPU-0-Why-Bother" about ML with clojure? I feel like many projects are abandoned and this one is full of blind links. Should one just use the usual suspects like tensorflow and their ports?

simongray 2021-01-11T09:06:17.418400Z

@christiaaan See #data-science

simongray 2021-01-11T09:10:26.422Z

It’s true that there’s been a few abandoneed projects. ML and data science never got the momentum to take off in Clojure. Some people are hard at work trying to make it happen. Dragan is one of them. Currently, the direction that people are moving seems to be developing better interop with languages like Python, R, Julia and other languages typically used for data-science.

simongray 2021-01-11T09:12:14.422500Z

Carin Meier has some tutorials for Clojure ML that use interop.

Christian 2021-01-11T09:13:51.422900Z

Hm, I was hoping my google-fu is weak and that this is not the answer

Christian 2021-01-11T09:14:28.423900Z

I'll check aut Carin Meier, that will get me a bit further I think. Thank you Simon

simongray 2021-01-11T09:15:48.425Z

NP. Go visit the #data-science channel too, especially the one on Zulip where the Clojure data science community is organised: https://clojurians.zulipchat.com/#narrow/stream/151924-data-science

Christian 2021-01-11T09:16:33.425400Z

Never heard of Zulip...

simongray 2021-01-11T09:16:49.425600Z

You have now πŸ˜‰

Christian 2021-01-11T09:17:40.425800Z

Looks like a slack-reskin πŸ˜„

Christian 2021-01-11T09:18:30.426500Z

no, it's different. Yeah, another platform. Sometimes I wish for one central thing for everything.

simongray 2021-01-11T09:20:00.426700Z

Also see https://scicloj.github.io/

simongray 2021-01-11T09:20:59.427100Z

Especially https://scicloj.github.io/pages/chat_streams/

πŸ‘€ 1
Christian 2021-01-11T09:21:15.427500Z

That's my currently open tab

πŸ‘ 1
Ramon Rios 2021-01-11T15:23:27.435100Z

Folks, i'm doing an exercise for a few time and not think in something smart enough to solve the problem: I'm trying to reduce a data-structure to show the amount of sectors per prefixes, example:

{"1"Β {"Technology"Β 2
Β Β Β Β Β Β Β Β "Clothing"Β 1}
Β Β Β "44"Β {"Banking"Β 1}}
I was able to reduce enough to have this :
[{:sector "Technology", :prefix "1"} {:sector "Clothing", :prefix "1"} {:sector "Technology", :prefix "1"} {sector "Banking", :prefix "44"}]
And then my mind blowup 😞 . How would you guys resolve this issue?

dpsutton 2021-01-11T15:24:22.435500Z

you want to end up with a datastructure like {"1" 2 "44" 1}?

Ramon Rios 2021-01-11T15:24:53.435600Z

like this

{"1"Β {"Technology"Β 2
Β Β Β Β Β Β Β Β "Clothing"Β 1}
Β Β Β "44"Β {"Banking"Β 1}}

dpsutton 2021-01-11T15:25:17.435800Z

oh that's desired, and the flat list is the starting point?

Ramon Rios 2021-01-11T15:25:27.436Z

Yes

dpsutton 2021-01-11T15:29:05.436200Z

(-&gt;&gt; [{:sector "Technology", :prefix "1"} {:sector "Clothing", :prefix "1"} {:sector "Technology", :prefix "1"} {:sector "Banking", :prefix "44"}]
     (group-by :prefix)
     (map (fn [[k vs]] [k (reduce (fn [m x] (update m (:sector x) (fnil inc 0)))
                                  {} vs)]))
     (into {}))

amarnah 2021-01-11T15:30:10.436400Z

You can use group-by :prefix to what you produced already.

user=&gt; (def h [{:sector "Technology", :prefix "1"} {:sector "Clothing", :prefix "1"} {:sector "Technology", :prefix "1"} {:sector "Banking", :prefix "44"}])
#'user/h
user=&gt; (group-by :prefix h)
{"1" [{:sector "Technology", :prefix "1"} {:sector "Clothing", :prefix "1"} {:sector "Technology", :prefix "1"}], "44" [{:sector "Banking", :prefix "44"}]}

Ramon Rios 2021-01-11T15:41:02.436500Z

Thank you

dpsutton 2021-01-11T15:43:46.436700Z

of course

Antonio Bibiano 2021-01-11T16:31:36.437Z

you can also try to use something like update-in

roelof 2021-01-11T16:32:59.437800Z

I have studied a chapter about concurrency.

roelof 2021-01-11T16:33:29.438500Z

but I wonder if I want to use slurp do I then also need to use a future ?

dpsutton 2021-01-11T16:35:06.438700Z

nope

dpsutton 2021-01-11T16:38:18.440Z

(slurp "project.clj") should work just fine. no need for future. you can of course use other threads and async mechanisms as you like but certainly no need to

roelof 2021-01-11T16:38:30.440300Z

oke, wierd then that promises and futures are explained and you do not need them on the challenges

dpsutton 2021-01-11T16:39:08.440700Z

i don't know what exercises you're going through but i don't have high confidence in them from what i've seen

roelof 2021-01-11T16:39:54.441200Z

im doing the exercices from the brave book @dpsutton

dpsutton 2021-01-11T16:40:16.441600Z

oh. never mind then. i thought you were going through something else.

dpsutton 2021-01-11T16:40:22.441800Z

i like that book

roelof 2021-01-11T16:40:44.442200Z

im now at the first chaptre about concurrency

roelof 2021-01-11T16:41:32.443200Z

a few weeks ago I tried the "clojure from the ground up" but that did not explain things well and wierd challenges to my opnion

Antonio Bibiano 2021-01-11T16:42:09.443500Z

(defn updater
  [result, m]
  (update-in result 
             [(:prefix m) (:sector m)] 
             #(if % (inc %) 1)))

(reduce updater {} my-input)

NPException 2021-01-11T16:43:17.444900Z

The usage of slurp in that chapter is for slurping from URLs. So as an exercise it's expected to be done in a future.

roelof 2021-01-11T16:43:39.445200Z

oke, I thought that already

NPException 2021-01-11T16:45:08.445800Z

but other than that there's indeed no good reason to use a future in those exercises.

Antonio Bibiano 2021-01-11T16:45:43.446200Z

about those I was a bit puzzled about getting the first result

Antonio Bibiano 2021-01-11T16:46:01.446900Z

looks like it's pretty hard with the current way google responds

roelof 2021-01-11T16:46:13.447400Z

Do not spoil it, I want to do it tomnorrow

isaac omy 2021-01-11T16:51:07.447900Z

hello from indonesia πŸ˜„

πŸ‘‹ 1
2021-01-11T16:53:24.449900Z

You put slurp in a future if you want to slurp things concurrently or do other things concurrently while you slurp. If you don't mind doing the slurp sequentially with other things you can use it directly without a future

βž• 1
roelof 2021-01-11T17:03:52.451400Z

hmm, it seems that (slurp "<https://google.nl/search?q%3Dclojure>") does not work, I do not see any urls or whatever returned back 😞

Antonio Bibiano 2021-01-11T17:05:45.451900Z

this gives a response

Antonio Bibiano 2021-01-11T17:05:47.452100Z

(slurp "<https://www.google.com/?q=Clojure>")

dpsutton 2021-01-11T17:06:22.452600Z

i get quite a bit back for that:

Antonio Bibiano 2021-01-11T17:06:33.453Z

but i don't think the response contains the results

2021-01-11T17:07:17.453200Z

at a glance, it seems like that massive payload of javascript would make the result show up, if you ran it

2021-01-11T17:07:46.453400Z

@roelof it's not accidental that it's usable in a browser but a big mess for machine consumption

NPException 2021-01-11T17:12:59.453600Z

Google's response has been much easier to digest years ago. It's unfortunately no longer the case

NPException 2021-01-11T17:14:34.454100Z

Just slurp does no longer work with google, since they expect a user agent header, otherwise they throw back a 403 at you.

pez 2021-01-11T17:16:04.455Z

It’s been a long day and I can’t figure this out (trying to create Hugo frontmatter from a list of things):

(str "foo:\n"
     (-&gt;&gt; [1 2 3]
          (map #(str "  - " %)))) ;=&gt; "foo:\nclojure.lang.LazySeq@103175be"

2021-01-11T17:16:26.455400Z

@pex the str for a lazy seq is basically broken

2021-01-11T17:16:32.455600Z

you can use pr-str

pez 2021-01-11T17:17:14.456Z

I get the same result if I just replace for pr-str, though.

2021-01-11T17:18:10.456300Z

user=&gt; (pr-str "foo:\n" (-&gt;&gt; [1 2 3] (map #(str " - " %))))
"\"foo:\\n\" (\" - 1\" \" - 2\" \" - 3\")"

2021-01-11T17:18:45.457Z

of course pr-str has the problem that it prints strings with " around them - you probably want to use format for more nuance

2021-01-11T17:18:59.457600Z

or string/join

pez 2021-01-11T17:19:08.457800Z

Ah, I replaced the wrong str πŸ˜ƒ

2021-01-11T17:20:09.458100Z

Just use mapv

2021-01-11T17:20:17.458400Z

@pez this is probably more like what you want

user=&gt; (str "foo:\n" (-&gt;&gt; [1 2 3] (map #(str " - " %)) (apply str)))
"foo:\n - 1 - 2 - 3"

❀️ 1
pez 2021-01-11T17:23:48.459500Z

Yes, that works great!

pez 2021-01-11T17:25:06.460100Z

mapv gave me this:

"foo:[\"  - 1\" \"  - 2\" \"  - 3\"]"

Antonio Bibiano 2021-01-11T17:25:15.460200Z

I think you might have better luck searching something like clojuredocs or github

NPException 2021-01-11T17:26:22.460400Z

Here's a little function you can use instead of slurp to get a result from google instead of an error code:

(defn request [url]
  (let [con (.openConnection (java.net.URL. url))]
    (.setRequestProperty con "user-agent" "Brave Clojure")
    (slurp (.getInputStream con))))

2021-01-11T17:27:10.460600Z

also your original string was broken: = is part of the url syntax but got escaped to %3d

roelof 2021-01-11T17:28:07.460800Z

hmm, or maybe use another function then

Antonio Bibiano 2021-01-11T17:31:26.461400Z

surely there must be a common library to do https requests?

roelof 2021-01-11T17:32:00.461600Z

@d.wetzel86 with your code I get the same mess back

2021-01-11T17:33:13.462400Z

Ya, it depends what you want exactly.

NPException 2021-01-11T17:34:10.462500Z

yeah, the mess google returns is a giant blob of javascript which contains the data that is then rendered on their page.

2021-01-11T17:35:24.463800Z

Also, I don't know if it's fair to say str on lazyseq is broken. I think it was intentional for it to not realize the elements

NPException 2021-01-11T17:35:52.464500Z

I didn't say it would make google return something useful, just that it won't return a 403 πŸ˜„ (which (slurp "<https://www.google.com/search?q=stackoverflow>") does for me)

2021-01-11T17:36:18.465200Z

A seq will also print its elements, it's only lazy-seq that doesn't. So I think it was on purpose.

roelof 2021-01-11T17:37:12.465300Z

oke

roelof 2021-01-11T17:37:47.465500Z

then it looks I cannot do the challenges from the chapter im now in. Pity

NPException 2021-01-11T17:39:48.465700Z

just use something else instead of google

Antonio Bibiano 2021-01-11T17:39:52.465900Z

my plan was to change where I search

roelof 2021-01-11T17:40:12.466100Z

Can i then not better use clj.http with this code : (client/get "<http://example.com/search>" {:query-params {"q" "foo, bar"}}) ?

NPException 2021-01-11T17:40:29.466300Z

sure

roelof 2021-01-11T17:40:56.466500Z

oke, then I have a plan for tomorrow

Antonio Bibiano 2021-01-11T17:41:06.466700Z

right now I think I will search http://clojuredocs.org

βž• 1
Antonio Bibiano 2021-01-11T17:41:15.466900Z

and was trying to find a place to search the java docs

NPException 2021-01-11T17:41:28.467100Z

another search engine that doesn't just return a blob of JS is Ecosia "<https://www.ecosia.org/search?q=stackoverflow>"

2021-01-11T17:50:48.468Z

@didibus that's false, it realizes all the elements in order to print the hash value it displays

roelof 2021-01-11T17:51:08.468400Z

that was challenge1 .

2021-01-11T17:51:11.468700Z

it can't calculate that hash if there are unrealized elements

2021-01-11T17:52:18.469800Z

Hum, you're right, so it is broken, weird. I thought that was the memory address, not the hash lol

2021-01-11T17:52:25.470Z

at this point it might be necessary to keep that printing behavior for legacy reasons, but the behavior is just broken

2021-01-11T17:52:57.470600Z

Even in a legacy reason, I can't really imagine how someone would rely on this behavior?

2021-01-11T17:53:46.471200Z

Also seems ClojureScript str doesn't do this, and returns a string of the elements between parens

alexmiller 2021-01-11T17:56:49.471600Z

this has been repeatedly mentioned various places but I am not aware of a jira / ask clojure question for it

alexmiller 2021-01-11T17:56:57.471800Z

is there one?

alexmiller 2021-01-11T17:57:41.472Z

if not, can we make one?

2021-01-11T18:01:51.472200Z

I can make one

1
2021-01-11T18:02:12.472400Z

wow - I had assumed this would be done to death by now, I guess everyone's just worked around it

2021-01-11T18:02:55.472700Z

Could it be this though? https://clojure.atlassian.net/browse/CLJ-2248

2021-01-11T18:03:10.472900Z

Seems a different issue, but maybe its the root cause of what's happening for str on lazy-seq?

2021-01-11T18:04:36.473100Z

Hum, on second look, I don't think so. I'll create an ask entry for it

2021-01-11T18:08:31.473300Z

Seems eduction suffers the same fate as lazy-seq

alexmiller 2021-01-11T18:09:14.473500Z

that one may have been intentional, don't recall for sure

2021-01-11T18:10:00.473700Z

At least eduction doesn't seem to execute the loop over the collection the way that str on lazy-seq does

alexmiller 2021-01-11T18:10:26.473900Z

eduction is a pending computation - I don't think you want it to eagerly evaluate on toString

alexmiller 2021-01-11T18:10:45.474100Z

similar to lazy seq in that regard

alexmiller 2021-01-11T18:11:15.474700Z

but seems like not printing and also forcing for hash can't make sense for lazyseq

2021-01-11T18:11:24.474900Z

Do you consider the "issue" to be that str on lazy-seq realize the elements, or that it doesn't stringify like seq does?

alexmiller 2021-01-11T18:12:01.475100Z

well I'm undecided w/o asking rich but it does not seem like the current behavior can make sense across both of those dimensions

2021-01-11T18:13:07.475300Z

Ok, I'll try and word it with both context then.

roelof 2021-01-11T18:22:00.476500Z

How do you find this sort of search engines ?

roelof 2021-01-11T18:22:05.476700Z

@d.wetzel86

NPException 2021-01-11T18:23:09.476900Z

That just happened to be one that I have heard of in the past. I saw Ecosia in an advert on YouTube once

roelof 2021-01-11T18:23:54.477400Z

oke

roelof 2021-01-11T18:23:58.477700Z

I never heard of it

roelof 2021-01-11T18:24:12.478100Z

maybe I try duckgogo also

NPException 2021-01-11T18:25:30.479Z

I checked that before I tried Ecosia. Unfortunately duckduckgo also returns mostly javascript

roelof 2021-01-11T18:26:12.479200Z

nope

roelof 2021-01-11T18:26:22.479400Z

I get a 500 here

(slurp "<https://duckduckgo.com/?q=clojure>")

NPException 2021-01-11T18:26:59.479700Z

yeah it also needs a user agent

roelof 2021-01-11T18:27:17.479900Z

hmm, I thinking I need 2 for the last assigment of the brave book

roelof 2021-01-11T18:27:41.480100Z

Create a new function that takes a search term and search engines as arguments, and returns a vector of the URLs from the first page of search results from each search engine.

roelof 2021-01-11T18:28:26.481Z

and another one . how do I get the urls from the output

mathpunk 2021-01-11T18:31:47.483700Z

I'm making a bunch of requests to a web service, and I think that I'm dropping a bunch of data because I don't know how to write async programs and I bet the web service doesn't want me to send 100 requests all at once. Is it time to learn core.async? Is that overpowered and I should learn about queues? Am I barking up the wrong tree?

NPException 2021-01-11T18:34:16.483800Z

I simply did not do that when I did the exercises... πŸ˜… If I remember correctly I just returned the length of the output for each response.

roelof 2021-01-11T18:36:13.484Z

oke, we see it tommorow. Found a few tutorials about web scraping that I want to try

NPException 2021-01-11T18:36:25.484200Z

I don't really know what the book author expects there. It seems like a very daunting task for someone who is just in the process of learning the language. Almost reminds me of the "Draw the rest of the f***ing owl" picture πŸ˜„

πŸ’― 1
roelof 2021-01-11T18:36:27.484400Z

now really time for family

roelof 2021-01-11T18:37:17.484600Z

I think its one for someone who is more familiar with clojure . The last one are always very advanced

NPException 2021-01-11T18:39:46.484800Z

I guess one fairly straightforward way would be to use a regex to extract all parts of the string that start with href=" and end with " .

roelof 2021-01-11T18:39:58.485Z

first find a second one who does not return a blob

NPException 2021-01-11T18:40:43.485200Z

(slurp "<https://clojuredocs.org/search?q=slurp>") ClojureDocs returns mostly HTML.

NPException 2021-01-11T18:41:17.485500Z

not a general purpose search engine, but good enough for the exercise

2021-01-11T18:41:56.485700Z

clojure or cljs?

roelof 2021-01-11T18:43:23.485900Z

lol

clyfe 2021-01-11T18:44:26.486100Z

(buffer n) - Returns a fixed buffer of size n. When full, puts will block/park.

roelof 2021-01-11T18:45:10.486600Z

GN all

clyfe 2021-01-11T18:45:32.486800Z

(chan 10) - Pass a number to create a channel with a fixed buffer

2021-01-11T18:45:54.487100Z

on the jvm, I wouldn't use core.async for an io problem unless most of the work needed was complex coordination of results

2021-01-11T18:46:11.487300Z

there's too many gotchas

roelof 2021-01-11T18:47:13.487500Z

I wanted to play with this tomorrow: https://practicalli.github.io/blog/posts/web-scraping-with-clojure-hacking-hacker-news/

roelof 2021-01-11T18:47:26.487700Z

looks if that can help me with the url parts

clyfe 2021-01-11T18:48:44.487900Z

on jvm, everything you put in the go block (http api calls, db driver calls, other io) must be via a non-blocking driver

clyfe 2021-01-11T18:49:03.488100Z

in js that's by default

2021-01-11T18:49:37.488300Z

sure, but on the jvm we also have http clients with built in thread pooling and throttling, we don't even have to open the core.async can of worms

πŸ‘ 1
2021-01-11T18:49:38.488500Z

I just don't know that adding core.async to "I don't know ho to write async programs" is going to fix anything

πŸ’― 1
πŸ˜† 2
NPException 2021-01-11T18:50:14.488800Z

that looks very nice. I have not heard of Enlive before. Will be a nice addition to my toolbelt πŸ˜„

2021-01-11T18:51:07.489Z

I might start with just introducing an Executor with a fixed size threadpool and run all your http requests on that

NPException 2021-01-11T18:52:22.489200Z

I've only written scraping code once before, and manually implemented searching for certain elements in the parsed DOM https://github.com/NPException/HC-Scraper/blob/master/src/hc_scraper/web.clj#L40-L57

mathpunk 2021-01-11T18:52:29.489600Z

it's on the jvm

clyfe 2021-01-11T18:52:35.489900Z

or, on jvm, wrap blocking calls in (a/thread ...)

2021-01-11T18:52:46.490100Z

an Executor in this case is basically a kind of work or job queue

mathpunk 2021-01-11T18:52:52.490300Z

hmmm i don't know most of the words you're using

mathpunk 2021-01-11T18:53:55.490900Z

an executor is a Java class?

2021-01-11T18:54:24.491100Z

Why even go async?

2021-01-11T18:55:14.491300Z

maybe checkout the examples in the readme https://github.com/TheClimateCorporation/claypoole (I haven't used claypoole, but I gather it provides a convenience layer over using executors directly)

mathpunk 2021-01-11T18:55:46.491600Z

for context, here's what I've been doing: forcing the promise to finish in a let statement, and then doing other stuff:

(defn- read
  [request]
  (let [response @(client/request request)]
    (-&gt; response :body (json/parse-string true))))

mathpunk 2021-01-11T18:56:22.491800Z

i just figure, that's gotta be wrong -- my reason for using the @ operator there is, gosh otherwise it just says "promise" instead being data I want

2021-01-11T18:56:47.492Z

The client returns you a promise?

2021-01-11T18:56:54.492200Z

ah, you must using the http client from http-kit?

mathpunk 2021-01-11T18:57:05.492400Z

maybe "async" isn't even the word! i just figure i need to do something so that if the web service is mad at me, I can exponentially back off (or whatever)

mathpunk 2021-01-11T18:57:07.492600Z

i am

2021-01-11T18:58:37.492800Z

http-kit is already going to be using some kind of queue internally for this stuff, so you may not need to care about it

mathpunk 2021-01-11T18:58:55.493Z

perhaps i should go ask in that channel..... i thought there was a chance this case was already handled

2021-01-11T18:59:13.493200Z

Ya, I'm not sure how http-kit handle errors on the promise

mathpunk 2021-01-11T18:59:15.493400Z

and that i'm just holding http-kit wrong

2021-01-11T18:59:17.493600Z

I mean, I don't use it, but internally http-kit has many queues and executors

clyfe 2021-01-11T18:59:44.493800Z

I don't see where core.aync involved; do you launch multiple of those read in parallel and suffocate the WS?

2021-01-11T19:00:24.494Z

Ok, you are supposed to check the result for an :error key it seems

2021-01-11T19:01:47.494200Z

(defn- read
  [request]
  (let [{:keys [body error]} @(client/request request)]
    (if error
      ;; handle error here
      (-&gt; body (json/parse-string true))))

mathpunk 2021-01-11T19:01:50.494400Z

@claudius.nicolae Maybe? I decided to act like the result of read was "just a data structure" and trusted that eventually things would break due to that totally not being true. I map that read function over a whole bunch of request maps

clyfe 2021-01-11T19:02:31.494700Z

map and not pmap, yeah?

mathpunk 2021-01-11T19:02:55.494900Z

yep, never touched pmap

2021-01-11T19:03:28.495100Z

You definitely don't need core.async. Http-kit is already async. You're just not using it correctly.

clyfe 2021-01-11T19:03:54.495300Z

ok so you do them in sequence and server still whines; check some of the http status codes for the dropped (may be 429)

clyfe 2021-01-11T19:04:46.495500Z

a sleep in between them may placate that WS; or waiting a day - some WS give you "x req / day"

clyfe 2021-01-11T19:05:06.495700Z

is that a short url expander by any chance?

2021-01-11T19:05:30.495900Z

From the code you provided, you are actually doing one request at a time

2021-01-11T19:05:45.496100Z

I suspect the issue is not that you are being throttled, just that you don't handle errors from the request

2021-01-11T19:06:01.496300Z

So when your request fails for some reason, you just drop it and never retry or anything

mathpunk 2021-01-11T19:06:07.496500Z

I'm reviewing the http-kit docs. Is this probably the use case? "Combined, concurrent requests, handle results synchronously" http://http-kit.github.io/client.html#combined

clyfe 2021-01-11T19:07:35.496800Z

That little @ makes your requests sequential

2021-01-11T19:08:22.497Z

Do you have to be concurrent? I would start sequential in your case, figure out why your requests are being dropped, fix that, than if you want to speed things up look into making it concurrent

mathpunk 2021-01-11T19:09:30.497200Z

:thumbsup::skin-tone-2:

2021-01-11T19:09:34.497400Z

Something like:

(defn- read
  [request]
  (let [{:keys [body error]} @(client/request request)]
    (if error
      [:error error]
      (-&gt; body (json/parse-string true))))

(mapv read coll-of-requests)

2021-01-11T19:09:58.497600Z

And like log a metric or an error for each instance of :error

mathpunk 2021-01-11T19:10:19.497900Z

yeah that makes sense, and then figure out it if's 429ing me or if something else is occurring

mathpunk 2021-01-11T19:11:21.498100Z

speed is not of the essence at all

mathpunk 2021-01-11T19:11:46.498300Z

thanks y'all!

2021-01-11T19:11:56.498500Z

and I find that API usage can't be as general as all that - eg. one service I've used refused to ever return an error code for failures, you'd need to look inside the json encoded body to check for errors

πŸ‘ 1
2021-01-11T19:12:08.498700Z

Yes, also, I don't know what your application is, but if it is a server, it could be concurrent from the requests made to it. So even though your requests to the API are sequential, your application could concurrently be making many sequential requests, and you could still be trottled.

2021-01-11T19:13:48.499Z

that's a great point - if your code is a server with built in parallelism, and the main thing you do is talk to someone else's API, a common pattern is that you end up with some stateful object providing access to that API (in the extreme case it can't even just coordinate on one vm - it has to collaborate with other vms providing the same service)

2021-01-11T19:14:28.499200Z

in order to respect limits that aren't imposed on the IP level, but on the credential level

mathpunk 2021-01-11T19:14:37.499400Z

hmm

mathpunk 2021-01-11T19:15:19.499600Z

i dunno about all that, I've just been working my way through the Gitlab API docs, because I'm tired of scrolling through the browser interface looking for what was the last passing/first failing job

2021-01-11T19:15:40.499800Z

oh then you don't need to mess with all that

mathpunk 2021-01-11T19:16:09Z

yeah this is a pretty bone-head program, which is why i wrote it on the "pretend it's local until it's clearly broken" principle

mathpunk 2021-01-11T19:16:11.000200Z

and that day has come

roelof 2021-01-11T19:30:46.000500Z

Wauw, I hope I ever can write like this

2021-01-11T22:28:15.007700Z

https://clojuredocs.org/clojure.core.async/thread says

Returns a channel which will receive the result of the body when completed, then close.
Is there a good way to tell if the launched thread died a horrible death? What would the &lt;! on the channel receive? I tried the following:
(a/go (println (a/&lt;! (a/thread (/ 1 0)))))
it prints out: nil and returns ManyToManyChannel

dpsutton 2021-01-11T22:33:41.008100Z

you can see what a/thread does here:

(defn thread-call
  "Executes f in another thread, returning immediately to the calling
  thread. Returns a channel which will receive the result of calling
  f when completed, then close."
  [f]
  (let [c (chan 1)]
    (let [binds (clojure.lang.Var/getThreadBindingFrame)]
      (.execute thread-macro-executor
                (fn []
                  (clojure.lang.Var/resetThreadBindingFrame binds)
                  (try
                    (let [ret (f)]
                      (when-not (nil? ret)
                        (&gt;!! c ret)))
                    (finally
                      (close! c))))))
    c))
it runs the body f in a try catch and will close the channel

2021-01-11T22:35:47.008400Z

What do you mean by: "Died a horrible death" ?

2021-01-11T22:38:47.008600Z

I guess any of these are interesting to me … although maybe one of them just doesn’t happen on the JVM? 1. thread crashes (seg faults?) … other threads keep running but this one evaporates 2. thread has an uncaught exception and comes to an β€œend” 3. thread is hanging β€” it made a call to something and the call never returns

2021-01-11T22:42:50.009Z

I guess based on what @dpsutton said, I can return some :happy-ending from the thread and if I get nil instead of that, then I’ll know something bad happened

2021-01-11T22:43:29.009200Z

since a take on a closed channel is nil

2021-01-11T22:44:49.009400Z

1. Seg fault would cause your application to crash and its process to terminate. 2. The channel will close and return nil. The UncaughtExceptionHandler will be called, if set. Otherwise exception just vanishes. 3. If thread is sleeping forever, or doing an infinite loop that will never end, then the <!! will wait forever as well.

1
dpsutton 2021-01-11T22:48:34.009700Z

the uncaught exception handler will never be called. there's a try catch immediately around the invocation of your code

2021-01-11T22:49:12.009900Z

Its called in my test

2021-01-11T22:50:17.010100Z

I think you assume the absence of a "catch" means that it catches everything and does nothing, but I think the absence of a "catch" means that it rethrows

2021-01-11T22:51:02.010300Z

Well, more that the exception is not caught, so it is still being thrown, even though a finally block will execute

2021-01-11T22:51:35.010500Z

I don't think it will count as a new "cause", so its not really rethrown, just continues to bubble up as normal

dpsutton 2021-01-11T22:53:02.010700Z

(oh i missed the lack of catch there. good eye)

πŸ˜‰ 1